咕咕咕~~~
话说这篇咕了好久到现在才开始写(暂时还是不会(day1T3),先欠着),后来到改才发现其实CSP2019不是很难?
先码题解
(day1T1)
傻逼题,不解释,确实对得起黄题的难度,这里思路是随便用个递归搞掉(1A)过算了
当然还可以用循环?,复杂度(O(logk))?
代码:
#include<bits/stdc++.h>
using namespace std;
#define ll unsigned long long
inline void read(ll &x)
{
x=0;char ch=getchar();
for(;!isalnum(ch);ch=getchar());
for(;isalnum(ch);ch=getchar()) x=x*10+ch-'0';
}
ll fac[64];
inline void sol(ll n,ll k)
{
if(n==1)
{
cout<<k?1:0;
return;
}
if(k>=fac[n-1])
{
cout<<1;
sol(n-1,fac[n-1]-k+fac[n-1]-1);
}
else
{
cout<<0;
sol(n-1,k);
}
}
int main()
{
ll n,k;read(n);read(k);
fac[0]=1;
for(register int i=1;i<=63;++i) fac[i]=fac[i-1]<<1;
sol(n,k);
return 0;
}
(day1T2)
额,话说我考场上连这道绿题都没切掉
天理难容啊
果然还是太蒟蒻了
想出了正解思路,但是脑袋有点乱,不会码,于是从(100pt)变成了(0pt),难受
一句话题解:一点(')')对答案的贡献是其对应的('(')的前一个符号的贡献(+1),开栈,回溯(dfs).
没了?~~没了 (T) _ (T) ……
代码(O(n)):
#include<bits/stdc++.h>
using namespace std;
#define ll long long
inline void read(int &x)
{
x=0;char ch=getchar();
for(;!isalnum(ch);ch=getchar());
for(;isalnum(ch);ch=getchar()) x=x*10+ch-'0';
}
const int xx=5e5+11;
int f[xx];
int head[xx],nxt[xx],to[xx],all=0;
ll k[xx],ctr[xx];
int sta[xx],top=0;
char ch[xx];
inline void dfs(int g)
{
int tmp=0;
if(ch[g]==')'&&top)
{
tmp=sta[top--];
ctr[g]=ctr[f[tmp]]+1;
}
if(ch[g]=='(') sta[++top]=g;
k[g]=k[f[g]]+ctr[g];
for(register int v=head[g];v;v=nxt[v])
dfs(to[v]);
if(tmp) sta[++top]=tmp;
if(ch[g]=='(') --top;
}
int main()
{
int n;read(n);
scanf("%s",ch+1);
for(register int i=2;i<=n;++i)
{
read(f[i]);
nxt[++all]=head[f[i]];
to[all]=i;
head[f[i]]=all;
}
dfs(1);
ll ans=0;
for(register int i=1;i<=n;++i)
ans^=(i*k[i]);
cout<<ans<<endl;
return 0;
}
(day1T3)
咕咕咕~~~
妈呀,黑题,不码了
咕咕咕~~~
(update~on~3rd~March~2020:)
额,那啥,真香?
其实应该还是能比较容易地找到规律的吧
目标肯定是尽量把最小的数字给转移到最小的编号上,而且转移的方案(即转移路线)有且仅有一个。既然如此,就容易联想到贪心了:每次从最小的数字的数字出发找到能到达的最小节点,将其输出,再将路线记录下来
因为如果由于前面的路线使得当前路线不成立,那么当前路线一定不是最优路线,故贪心肯定是合法的
接下来考虑贪心的性质:
很容易可以想到一条路径应当划分为初始节点(初点),初点所连的在该路径上的边(初边),路径上的其它边(途边)与点(途点),以及末点与末边:
- 对于初边,它应当是初点的第一条被删的边,否则必定无法达成目标,所以任何一个点有且仅有一个初边
- 对于末边,它应当是末点的最后一条被删边,否则必定无法达成目标,同样任何一个点有且仅有一个末边
- 对于途边,如果有另一条同一路径上的途边与其连接在同一个途点上,那么对这个途点来说,其所连的所有边的删除顺序中,这两条途边必定是一先一后紧连的,并且一对途边一定只能转运一个数字
所以可以考虑用(st[i](start))和(en[i](end))分别记录节点(i)的初边与末边,(pre[i][j])和(nxt[i][j])(均为(bool)型)来记录点(i)的边(j)在点(i)连边的删边顺序中是否有连在一起的前驱与后继,以便我们对当前贪心方案的合法性进行判断
接下来看合法情况是怎样的:
假设现在我们遍历到了数字(i)的对应节点:
能将(i)作为初点的条件:
- 目前没有初边或初边为当前遍历到的即将作为初边的边(预初边)
- 预初边在(i)的删除顺序中没有前驱
- 若预初边与(i)的末边已有直接的删除顺序连接,那么应当(i)的所有边的顺序都已确定(因为要将所有边删完)
能将(i)作为途点的条件:
- 连接的两条途边分别在(i)无前驱与后继
- 两条途边没有直接的删除顺序连接
- 若靠前途边与(i)初边有直接删除顺序连接,靠后途边与(i)末边有直接删除顺序连接,那么应当除这两途边之外所有边的顺序都已确定(因为要将所有边删完)
能将(i)作为末点的条件
- 目前没有末边或末边为当前遍历到的即将作为末边的边(预末边)
- 预末边在(i)的删除顺序中没有后继
- 若预末边与(i)的初边已有直接的删除顺序连接,那么应当(i)的所有边的顺序都已确定(因为要将所有边删完)
可以发现,前驱与后继都只是需要查看是否有,故仅需对每个点的每条边开个(bool)型数组判断是否有。而每个点的初边与末边我们都是需要知道具体是哪个的,故要对每个点存下这个点的初边与末边编号
接下来就是一个小问题:是否有直接删除顺序相连。对此,容易想到使用并查集,当两条边确定了在某个点的严格先后顺序之后,将两条边在这个节点的并查集进行合并,同时可以使用并查集查看两条边是否在一个删边系统中
然后就是最后一个问题:如何确定当前节点删边顺序都已确定?
首先看删边顺序是怎么确定的:利用途边之间的连接得到一对途边在某一途点的严格先后顺序,然后一段段连接得到完全段。由此我们发现,当前节点的删边顺序是由一个个的严格删边顺序组合成的,而若一个点的删边顺序已完全确定,那么这个点对应得到的严格删边顺序数一定是该点度数减一。于是考虑记录每个点的度数,当确定一对严格删边顺序,度数减一
暴力枚举每个点与当前节点是否相通再取(min)的复杂度是(O(n^3))的,我们当然不会这么傻,考虑每次以当前节点为根进行(dfs)遍历所有能到的点再取(min),复杂度降为(O(n^2)),而每次(dfs)时修改路径上每个点的父亲((f))节点,这样在后面更新时会方便一点,直接从末点一个个往上蹦就行了
由于要开并查集,所以复杂度应当套个(log),但由于是对每个点的边开并查集,一条边最多开两次,用的次数也很有限,所以总的复杂度应该是(O(Tn^2alpha n)),还是怀疑CCF老爷机跑不跑得过
代码:
#include<bits/stdc++.h>
using namespace std;
inline int read()
{
int x=0;char ch=getchar();
for(;!isalnum(ch);ch=getchar());
for(;isalnum(ch);ch=getchar()) x=x*10+ch-'0';
return x;
}
const int xx=2e3+10;
struct node
{
int fa[xx];
bool pre[xx],nxt[xx];
inline void init(int n)
{
for(register int i=1;i<=n;++i)
{
fa[i]=i;
pre[i]=nxt[i]=0;
}
}
inline int find(int i)
{
return i==fa[i]?i:fa[i]=find(fa[i]);
}
inline bool check(int i,int j)
{
return find(i)==find(j);
}
inline void merge(int i,int j)
{
int a=find(i),b=find(j);
if(a!=b)
fa[a]=b;
}
}drop[xx];
int dot[xx],st[xx],en[xx],f[xx],f_l[xx],n;
int hd[xx],to[xx<<1],nt[xx<<1],du[xx],cnt;
inline void init()
{
cnt=1;
for(register int i=1;i<=n;++i)
drop[i].init(n),f[i]=hd[i]=st[i]=en[i]=du[i]=0;
}
inline void add(int u,int v)
{
nt[++cnt]=hd[u],to[hd[u]=cnt]=v;
nt[++cnt]=hd[v],to[hd[v]=cnt]=u;
++du[v],++du[u];
}
inline int dfs(int g,int jt)
{
int res=xx+1;
if(jt)
if(!en[g]||en[g]==jt)
if(!drop[g].nxt[jt])
if(!(drop[g].check(jt,st[g])&&st[g]&&du[g]>1))
res=g;
for(register int i=hd[g],j;i;i=nt[i])
{
j=i>>1;
if(j==jt)
continue;
if(!jt)
{
if(!st[g]||st[g]==j)
if(!drop[g].pre[j])
if(!(drop[g].check(j,en[g])&&en[g]&&du[g]>1))
f[to[i]]=g,f_l[to[i]]=j,res=min(res,dfs(to[i],j));
}
else
{
if(jt!=en[g]&&j!=st[g]&&!drop[g].check(jt,j))
if(!drop[g].nxt[jt]&&!drop[g].pre[j])
if(!(du[g]>2&&st[g]&&en[g]&&drop[g].check(st[g],jt)&&drop[g].check(en[g],j)))
f[to[i]]=g,f_l[to[i]]=j,res=min(res,dfs(to[i],j));
}
}
return res;
}
inline void mate(int g)
{
int jt;
jt=en[g]=f_l[g];
g=f[g];
while(f[g])
{
drop[g].nxt[f_l[g]]=drop[g].pre[jt]=1;
drop[g].merge(jt,f_l[g]);
--du[g];
jt=f_l[g];
g=f[g];
}
st[g]=jt;
}
int main()
{
int T=read();
while(T--)
{
n=read();
if(n==1)
{
puts("1");
continue;
}
init();
for(register int i=1;i<=n;++i)
dot[i]=read();
for(register int i=2,u,v;i<=n;++i)
u=read(),v=read(),add(u,v);
for(register int i=1,j;i<=n;++i)
{
f[dot[i]]=0;
j=dfs(dot[i],0);
mate(j);
printf("%d ",j);
}
puts("");
}
return 0;
}
(day2T1)
话说硬是没看出来这是(DP),一直以为是数学,想到了容斥,结果没思路,想打份(O(n2^m))的爆搜的,结果不知道哪里锅了,没调出来
自闭.jpg
于是看到(DP)就醍醐灌顶了
但想不到真的不能怪我啊,蒟蒻dp不好
容斥思路走一波,显然若有超过(k/2)的食材则仅有一种,于是总方案减不合法方案即为所求
设(s_i)为第(i)行种类数总和,(ex_{i,j})为第(i)行除(a_{i,j})种类数总和,(f_{i,j})为当前列(不合法列)(DP)到第(i)行,并且当前列的选择比其他列多(j)种的方案数((jin [-n,n])),(g_{i,j})为当前(整体)(DP)到第(i)行,并且已经选择了(j)种的方案数
易知:
时间复杂度处理(f_{i,j})是(O(mn^2)),(g_{i,j})是(O(n^2)),合起来是(O(mn^2)),跑满大概(2e7),严重怀疑CCF老爷机跑不跑得动
上代码:
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll mod=998244353;
const int xn=110,xm=2010;
inline void read(ll &x)
{
x=0;char ch=getchar();
for(;!isalnum(ch);ch=getchar());
for(;isalnum(ch);ch=getchar()) x=x*10+ch-'0';
}
ll a[xn][xm],s[xn],ex[xn][xm];
ll f[xn][xn<<1],g[xn][xn];
ll ans1=0,ans2=0,n,m;
inline void build_f()
{
for(register int i=1;i<=m;++i)
{
memset(f,0,sizeof(f));
f[0][n]=1;
for(register int j=1;j<=n;++j)
for(register int k=n-j;k<=n+j;++k)
{
f[j][k]=f[j-1][k];
(f[j][k]+=(f[j-1][k-1]*a[j][i]%mod))%=mod;
(f[j][k]+=(f[j-1][k+1]*ex[j][i]%mod))%=mod;
}
for(register int k=n+1;k<=(n<<1);++k) (ans2+=f[n][k])%=mod;
}
}
inline void build_g()
{
g[0][0]=1;
for(register int j=1;j<=n;++j)
for(register int k=1;k<=j;++k)
{
g[j][k]=g[j-1][k];
(g[j][k]+=(k>1?(g[j-1][k-1]*s[j]%mod):s[j]))%=mod;
}
for(register int k=1;k<=n;++k) (ans1+=g[n][k])%=mod;
}
int main()
{
read(n);read(m);
for(register int i=1;i<=n;++i)
{
s[i]=0;
for(register int j=1;j<=m;++j)
{
read(a[i][j]);
(s[i]+=a[i][j])%=mod;
}
for(register int j=1;j<=m;++j) ex[i][j]=((s[i]-a[i][j])%mod+mod)%mod;
}
build_f();
build_g();
ll ans=((ans1-ans2)%mod+mod)%mod;
printf("%lld
",ans);
return 0;
}
day2T2
day2T2好像也不是很难,就是切不掉而已
首先要证明一个大家考场上都应该想到了的显而易见的结论:当最后一段最小时肯定最优
同理可得整段亦如此,因为整段也是由一个个小段连成的
所以可以想到(DP),记录当前(i)的最优解对应的最后一段的和(l_i),同时需要前缀和(s_i)
那么遍历到(i)时,所求即为
移项可以发现条件变为
由于(l_k+s_k)单增,所以可以用单调队列咕咕咕~ ~ ~
可是用了大整数((1e9)压位)发现会(T),最多咕一个(92pt),貌似考后的某些关于(ntt)的想法还是没错的,如果没有(luogu) __ (int128)那这道题就萎了
(O2爷除外)
时间复杂度是非常友好的(O(n))
代码((92pt)的(1e9)压位,把注释去掉可以用__(int128),或者可以就这样(O2)过)
#include<bits/stdc++.h>
using namespace std;
#define ll long long
inline ll read()
{
ll x=0;char ch=getchar();
for(;!isalnum(ch);ch=getchar());
for(;isalnum(ch);ch=getchar()) x=x*10+ch-'0';
return x;
}
const ll digit=1e9,mod=1ll<<30;
const int xx=4e7+11;
ll s[xx],que1[xx],ans[10],tmp[10],tmpp[10],hd=1,tl=0;
int a[xx],que2[xx];
int n,op;
ll x,y,z,b1,b2,m;
ll p,l,r,tmp_b;
inline void copy(ll num)
{
tmp[0]=0;
while(num)
{
tmp[++tmp[0]]=num%digit;
num/=digit;
}
memset(tmpp,0,sizeof(tmpp));
for(register int i=1;i<=tmp[0];++i)
for(register int j=1;j<=tmp[0];++j)
{
tmpp[i+j-1]+=tmp[i]*tmp[j];
if(tmpp[i+j-1]>=digit)
{
tmpp[i+j]+=tmpp[i+j-1]/digit;
tmpp[i+j-1]%=digit;
}
}
tmpp[0]=tmp[0]*2-1;
for(register int i=1;i<=tmpp[0];++i)
if(tmpp[i]>=digit)
{
tmpp[i+1]+=(tmpp[i]/digit);
tmpp[i]%=digit;
}
while(!tmpp[tmpp[0]]) --tmpp[0];
}
inline void count()
{
int k=max(ans[0],tmpp[0])+1;
for(register int i=1;i<=k;++i)
{
ans[i]+=tmpp[i];
if(ans[i]>=digit)
{
ans[i+1]+=(ans[i]/digit);
ans[i]%=digit;
}
}
while(!ans[k]) --k;
ans[0]=k;
}
int main()
{
n=read();op=read();
if(!op)
for(register int i=1;i<=n;++i)
a[i]=read();
else
{
x=read();y=read();z=read();
b1=read();b2=read();m=read();
for(register int i=1,j=1;i<=m;++i)
{
p=read(),l=read(),r=read();
for(;j<=p;++j)
{
a[j]=b1%(r-l+1ll)+l;
tmp_b=((x*b2%mod+y*b1%mod)%mod+z)%mod;
b1=b2;b2=tmp_b;
}
}
}
for(register int i=1;i<=n;++i)
s[i]=s[i-1]+a[i],a[i]=0;
que2[++tl]=0;que1[tl]=0;
que2[++tl]=1;que1[tl]=2ll*s[1];
for(register ll i=2,j;i<=n;++i)
{
while(que1[hd+1]<=s[i]&&hd+1<=tl) ++hd;
a[i]=que2[hd];
j=2ll*s[i]-s[a[i]];
while(j<=que1[tl]) --tl;
que2[++tl]=i;que1[tl]=j;
}
for(register int i=n,j=a[n];i;i=a[i],j=a[j])
copy(s[i]-s[j]),count();
// __int128 res=0;
// for(register int i=n,j=a[n];i;i=a[i],j=a[j])
// res+=((__int128)(s[i]-s[j]))*(s[i]-s[j]);
// while(res)
// ans[++ans[0]]=res%10,res/=10;
for(register int i=ans[0];i>=1;--i)
printf("%d",ans[i]);
puts("");
return 0;
}
(day2T3)
考试的时候还是只会打暴力,蒟蒻~ ~ ~
关于正解,可以倍增
首先要明确一个性质,对于一棵树上的某个点,假设它是根,那么当它不是这棵树重心时,重心必在它重儿子所在的子树上
归纳一下,当它不是根时,那么重心一定在全树除该子树或者其重儿子子树上
通过比较一下(sz[root]-sz[i])与(sz[son[i]])的大小就可以了
于是可以考虑用倍增跳这个路径,直到找到重心为止
考虑(dfs)枚举每条边要删的边,再从这条边的两头跳重心,由于可能要删的边截取了一个点的重儿子,所以我们的次重儿子也要存好,方便找到真正的路径,同时更新我们的倍增路径与子树大小,然后再慢慢跳
跳完之后注意处理与重心相连的几个点也有可能成为重心
两次(dfs),先预处理再(solve),简单易懂,时间复杂度(O(nlogn))
代码:
#include<bits/stdc++.h>
using namespace std;
#define ll long long
inline int read()
{
int x=0;char ch=getchar();
for(;!isalnum(ch);ch=getchar());
for(;isalnum(ch);ch=getchar()) x=x*10+ch-'0';
return x;
}
const int xx=3e5;
int hd[xx],nxt[xx<<1],to[xx<<1],all;
int f[xx],son_1[xx],son_2[xx],sz[xx];
int d_f[xx],d_son[xx],d_sz[xx],way[xx][20];
ll ans=0;
inline ll test(int g,int num)
{
ll res=(max(d_sz[d_son[g]],num-d_sz[g])<=num/2)?1:0;
return res*g;
}
inline void count(int g)
{
int w=g;
for(register int i=19;~i;--i)
if(d_sz[way[w][i]]>=ceil((double)d_sz[g]/2.0))
w=way[w][i];
ans+=(test(w,d_sz[g])+test(d_son[w],d_sz[g])+test(d_f[w],d_sz[g]));
}
inline void dfs_1(int g)
{
sz[g]=1;
for(register int u=hd[g],v;u;u=nxt[u])
{
v=to[u];
if(v==f[g]) continue;
f[v]=g;
dfs_1(v);
sz[g]+=sz[v];
if(sz[v]>sz[son_1[g]]) son_2[g]=son_1[g],son_1[g]=v;
else if(sz[v]>sz[son_2[g]]) son_2[g]=v;
}
way[g][0]=son_1[g];
for(register int i=1;i<=19;++i)
way[g][i]=way[way[g][i-1]][i-1];
}
inline void dfs_2(int g)
{
for(register int u=hd[g],v;u;u=nxt[u])
{
v=to[u];
if(v==f[g]) continue;
d_f[g]=d_f[v]=0;
d_sz[g]=sz[1]-sz[v];
if(v==son_1[g]) d_son[g]=son_2[g];
else d_son[g]=son_1[g];
if(d_sz[d_son[g]]<d_sz[f[g]]) d_son[g]=f[g];
way[g][0]=d_son[g];
for(register int i=1;i<=19;++i)
way[g][i]=way[way[g][i-1]][i-1];
count(g),count(v);
d_f[g]=v;
dfs_2(v);
}
d_f[g]=f[g],d_sz[g]=sz[g],d_son[g]=way[g][0]=son_1[g];
for(register int i=1;i<=19;++i)
way[g][i]=way[way[g][i-1]][i-1];
}
int main()
{
int T=read();
while(T--)
{
memset(hd,0,sizeof(hd));
memset(son_1,0,sizeof(son_1));
memset(son_2,0,sizeof(son_2));
memset(f,0,sizeof(f));
int n=read();all=ans=0;
for(register int i=2,u,v;i<=n;++i)
{
u=read(),v=read();
nxt[++all]=hd[u],to[hd[u]=all]=v;
nxt[++all]=hd[v],to[hd[v]=all]=u;
}
dfs_1(1);
for(register int i=1;i<=n;++i)
d_sz[i]=sz[i],d_son[i]=son_1[i],d_f[i]=f[i];
dfs_2(1);
printf("%lld
",ans);
}
return 0;
}