2021.6.17考试总结[NOIP模拟8]
T1 星际旅行
其实就是求两条只走一遍的边的方案数。
考场上第一眼就感觉不可做,后来画了几个图,发现好像只要两个边是相连的就可以只走一遍,居然还真拿了30。。
其实是一道欧拉路的题,把每条非自环的边看作两条平行的边,问题就转变为了删掉两条边,使图变为欧拉图。
欧拉图存在的充要条件是图联通,且只有0或2个点的出度为奇数。因为把边一分为二,所以初始出度都为偶。
所以删两条相连的边是其中一种情况,30pts到手。
另外考虑自环,由于自环不计入出度,所以可以删掉两个自环或一个自环和任意一边。
注意在计算前要先判断图是否联通,且应该是边联通,也就是说需要边能够一次遍历完,跟点没关系。
最后把答案累加即可。
code:
1 #include<bits/stdc++.h> 2 #define int long long 3 using namespace std; 4 int n,m,to[200001],nex[200001],head[100001],num,out[100001],ans,cir; 5 bool vis[100001]; 6 inline int read(){ 7 int x=0,f=1; 8 char ch=getchar(); 9 while(ch<'0'||ch>'9') 10 { 11 if(ch=='-') f=-1; 12 ch=getchar(); 13 } 14 while(ch<='9'&&ch>='0') 15 { 16 x=(x<<1)+(x<<3)+(ch^48); 17 ch=getchar(); 18 } 19 return x*f; 20 } 21 void write(int x){ 22 if(x<0) putchar('-'), x=-x; 23 if(x>9) write(x/10); 24 putchar(x%10+'0'); 25 } 26 inline void add(int a,int b){ 27 if(a==b){ cir++; return; } 28 to[++num]=b; nex[num]=head[a]; head[a]=num; out[a]++; 29 to[++num]=a; nex[num]=head[b]; head[b]=num; out[b]++; 30 } 31 void dfs(int s){ 32 vis[s]=1; 33 for(int i=head[s];i;i=nex[i]) 34 if(!vis[to[i]]) dfs(to[i]); 35 } 36 signed main(){ 37 n=read(); m=read(); 38 for(int i=1;i<=m;i++) add(read(),read()); 39 for(int i=1;i<=n;i++) 40 if(out[i]){ dfs(i); break; } 41 for(int i=1;i<=n;i++) 42 if(!vis[i]&&out[i]){ write(0); putchar('\n'); return 0; } 43 if(!num){ write(0); putchar('\n'); return 0; } 44 for(int i=1;i<=n;i++) ans+=out[i]*(out[i]-1)/2; 45 ans+=cir*(cir-1)/2+cir*num/2; 46 write(ans); putchar('\n'); 47 return 0; 48 }
T2 砍树
考场上看到这题,哎呦我*这不二分吗。。啥都不说打了个天才二分,结果又是30。。
实际上这道题中d和k并没有线性关系,d越小k不一定越小,所以二分是假的,拿30算是很高了。
正解是个没学过的暴力一样的东西。
要求的d要满足
化简一波,令,则
。
运用整除分块,天花板函数图像应是几节平行于x轴的线,每条线左边界为l,右边界为r,则有:
只有右端点可能为最优解。每次找出右端点后检验是否在取值范围,更新答案即可。
code:
1 #include<bits/stdc++.h> 2 #define int long long 3 using namespace std; 4 int n,k,a[101],d,num,l,r,ans; 5 inline int read(){ 6 int x=0,f=1; 7 char ch=getchar(); 8 while(ch<'0'||ch>'9') 9 { 10 if(ch=='-') f=-1; 11 ch=getchar(); 12 } 13 while(ch<='9'&&ch>='0') 14 { 15 x=(x<<1)+(x<<3)+(ch^48); 16 ch=getchar(); 17 } 18 return x*f; 19 } 20 void write(int x){ 21 if(x<0) putchar('-'), x=-x; 22 if(x>9) write(x/10); 23 putchar(x%10+'0'); 24 } 25 bool check(int d){ 26 int res=0; 27 for(int i=1;i<=n;i++) res+=ceil((double)a[i]/d); 28 return res*d<=num; 29 } 30 signed main(){ 31 n=read(); k=read(); num+=k; 32 for(int i=1;i<=n;i++) a[i]=read(), num+=a[i]; 33 for(l=1;l<=num;l=r+1){ 34 r=(num/(num/l)); 35 if(check(r)) ans=r<ans?ans:r; 36 } 37 write(ans); putchar('\n'); 38 return 0; 39 }
T3 超级树
比T1更不可做。。赛时最高15分。。
一看题解,果然没思路的题都是DP
考虑状态数组fi,j表示一颗深度为i的超级树,有j条符合题意的路线的方案数。
每次转移在当前超级树上方加一个根节点,考虑fi对fi+1的影响。
枚举当前超级树左子树路径为l,右子树路径为r,记num=fi,l*fi,r,则有:
• 什么也不做 dp[i+1][l+r]+=num
• 根自己作为一条新路径 dp[i+1][l+r+1]+=num
• 根连接到左子树(或右子树)的某条路径上 dp[i+1][l+r]+=2*num*(l+r)
• 根连接左子树和右子树的各一条路径 dp[i+1][l+r-1]+=2*num*l*r
• 根连接左子树(或右子树)的两条路径 dp[i+1][l+r-1]+=num*(l*(l-1)+r*(r-1))
初值赋f1,0和f1,1为1,最后答案为fk,1。
空间看似会炸,但因为每次第二维最多减一,所以最多只有第二维为k的状态可能转移到最终状态,所以考虑这前k个即可。
几个式子合并一下,开个longlong,少模几次,不然可能会T(至少我们这小破评测机是这样的
code:
1 #include<bits/stdc++.h>//T3 2 #define int long long 3 using namespace std; 4 int k,mod,f[301][301]; 5 inline int read(){ 6 int x=0,f=1; 7 char ch=getchar(); 8 while(ch<'0'||ch>'9') 9 { 10 if(ch=='-') f=-1; 11 ch=getchar(); 12 } 13 while(ch<='9'&&ch>='0') 14 { 15 x=(x<<1)+(x<<3)+(ch^48); 16 ch=getchar(); 17 } 18 return x*f; 19 } 20 void write(int x){ 21 if(x<0) putchar('-'), x=-x; 22 if(x>9) write(x/10); 23 putchar(x%10+'0'); 24 } 25 signed main(){ 26 k=read(); mod=read(); 27 f[1][1]=f[1][0]=1; 28 for(register signed i=0;i<k;++i) 29 for(register signed j=0;j<k;++j) 30 for(register signed l=0;l<=j;++l){ 31 int r=j-l,num=f[i][l]*f[i][r]%mod; 32 f[i+1][j]=(f[i+1][j]+num+2*num*j)%mod; 33 f[i+1][j+1]=(f[i+1][j+1]+num)%mod; 34 f[i+1][j-1]=(f[i+1][j-1]+2*num*l*r+num*(l*(l-1)+r*(r-1)))%mod; 35 } 36 write(f[k][1]); putchar('\n'); 37 return 0; 38 }
T4 求和
超级大水题。。数据水的一匹
就求出lca,然后暴力累乘就完事。当时根本没想A,一出分人都傻了
洛谷上这题有个子任务hack暴力,可以用前缀和优化,预处理出根节点到每个点k次幂的前缀和,然后进行O(1)查询,常数大但复杂度小。
但我还是懒得没去打。。
code:
1 #include<bits/stdc++.h>//T4 2 #define int long long 3 #define rsigned register signed 4 using namespace std; 5 const int p=998244353; 6 int num,head[300001],nex[600001],to[600001],fa[300001][20],n,t,dep[300001],mem[300001][51]; 7 inline int read(){ 8 int x=0,f=1; 9 char ch=getchar(); 10 while(ch<'0'||ch>'9') 11 { 12 if(ch=='-') f=-1; 13 ch=getchar(); 14 } 15 while(ch<='9'&&ch>='0') 16 { 17 x=(x<<1)+(x<<3)+(ch^48); 18 ch=getchar(); 19 } 20 return x*f; 21 } 22 void write(int x){ 23 if(x<0) putchar('-'), x=-x; 24 if(x>9) write(x/10); 25 putchar(x%10+'0'); 26 } 27 inline int qmod(int a,int b){ 28 if(mem[a][b]) return mem[a][b]; 29 int res=1,A=a,B=b; a%=p; 30 while(b){ 31 if(b&1) res=res*a%p; 32 a=a*a%p; 33 b>>=1; 34 } 35 mem[A][B]=res; 36 return mem[A][B]; 37 } 38 inline void add(int a,int b){ 39 to[++num]=b; nex[num]=head[a]; head[a]=num; 40 to[++num]=a; nex[num]=head[b]; head[b]=num; 41 } 42 void bfs(){ 43 dep[1]=0; t=log2(n)+1; 44 queue<signed>q; q.push(1); 45 while(!q.empty()){ 46 signed x=q.front(); q.pop(); 47 for(rsigned i=head[x];i;i=nex[i]){ 48 signed y=to[i]; 49 if(y==1||dep[y]) continue; 50 dep[y]=dep[x]+1; 51 fa[y][0]=x; 52 for(signed j=1;j<=t;j++) fa[y][j]=fa[fa[y][j-1]][j-1]; 53 q.push(y); 54 } 55 } 56 } 57 inline int lca(int x,int y){ 58 if(x==1||y==1) return 1; 59 if(dep[x]>dep[y]) swap(x,y); 60 for(rsigned i=t;i>=0;i--) 61 if(dep[fa[y][i]]>=dep[x]) y=fa[y][i]; 62 if(x==y) return x; 63 for(rsigned i=t;i>=0;i--) 64 if(fa[y][i]!=fa[x][i]) y=fa[y][i], x=fa[x][i]; 65 return fa[x][0]; 66 } 67 signed main(){ 68 n=read(); 69 for(signed i=1;i<n;i++) add(read(),read()); 70 rsigned m=read(); 71 bfs(); 72 while(m--){ 73 int a=read(),b=read(),c=read(),ans=0; 74 int ca=lca(a,b); 75 while(a!=ca) ans=(ans+qmod(dep[a],c))%p, a=fa[a][0]; 76 while(b!=ca) ans=(ans+qmod(dep[b],c))%p, b=fa[b][0]; 77 ans=(ans+qmod(dep[ca],c))%p; 78 write(ans); putchar('\n'); 79 } 80 return 0; 81 }