2019-7-18 考试总结
A. 星际旅行
这是一个欧拉图的题。。
因为题目说只有$2$条边走一次,剩下的$m-2$条边都走两次,
把这$m$条双向边都拆成$2$条单向边,
最后就变成了删除两条边并且让剩下的拆完的边成为一条欧拉路,(但是考试的时候想不到。。)
欧拉路指的是从起点$S$开始走所有边一次,最后走到终点$T$,$T$与$S$可以不同。
有向图中欧拉路判定就是 有且仅有$0$或$2$个点的出度和入度不相等。
只要枚举每个点$i$,在它的所有边中选择两条边删除,并且这两条边必须一个起点是$i$,一个终点是$i$。
这对答案的贡献是$\frac {n\times (n-1)}{2}$
至于为什么要除以$2$,好好看题,路径的本质不同的含义。
然后就是自环的贡献。
另外题目没说联通,所以要判一下整张图的联通性,
如果有多个子图并且这些子图中有多个子图包含的路径数大于等于$1$,
那么方案数肯定为$0$。
丑陋的代码:
#include<iostream> #include<cstring> #include<cstdio> #define Maxn 500050 #define Reg register using namespace std; int n,m,tot,zh,nmp,fat[Maxn],fir[Maxn],vis[Maxn],num[Maxn]; long long ans; struct Tu {int st,ed,next;} lian[Maxn*100]; void add(int x,int y) { lian[++tot].st=x; lian[tot].ed=y; lian[tot].next=fir[x]; fir[x]=tot; return; } int getf(int x) { if(fat[x]==x) return x; else return fat[x]=getf(fat[x]); } int main() { scanf("%d%d",&n,&m); for(Reg int i=1,x,y;i<=m;++i) { scanf("%d%d",&x,&y); if(x==y) ++zh; else add(x,y),add(y,x); } for(Reg int i=1,p;i<=n;++i) fat[i]=i; for(Reg int i=1;i<=tot;++i) { int p=getf(lian[i].st); int q=getf(lian[i].ed); if(p!=q) fat[p]=q; } for(Reg int i=1;i<=n;++i) { int p=getf(i); ++num[p]; if(!vis[p]) { vis[p]=1; ++nmp; } } for(Reg int i=1,p;i<=n;++i) { p=0; for(Reg int j=fir[i];j;j=lian[j].next) ++p; ans+=(long long)p*(p-1)/2; } if(zh) { ans+=(long long)zh*(m-zh); ans+=(long long)zh*(zh-1)/2; } if(nmp==1) printf("%lld",ans); else { int ok=0; for(Reg int i=1;i<=n;++i) if(num[i]>=2) ++ok; if(ok==1) printf("%lld",ans); else printf("0"); } return 0; }
B. 砍树
很容易得到满足题目要求的一个式子:
$\sum \lceil \frac{a_i}{d}\rceil \times d-\sum a_i \leq k$
把这东西移项然后化简能得到:
$\sum \lceil \frac{a_i}{d}\rceil\leq \lfloor \frac{k+\sum a_i}{d}\rfloor$
把$k+\sum a_i$记作$C$,
那么原式就是 $\sum \lceil \frac{a_i}{d}\rceil\leq \lfloor \frac{C}{d}\rfloor$
右边的那个式子可以用数论分块的方法(虽然我不太会)
右边的那些式子是分段的,也就是在某一段区间内它的值是不会变的,
如果知道左端点的话,那么右端点是可以计算出来的,这个式子是长这个样子的: $R=\left \lfloor\frac{C}{\lfloor \frac{C}{L}\rfloor}\right \rfloor$。
左边的这个式子是单调的,随着$d$的增大,它是递减的。
那么在每一个区间内,如果有不满足上边式子的一个$d$值,小于它的$d$也肯定不成立的,
而我们要找的是$d$的最大值,那么就只需要判断每一个的右端点是否合法,
如果合法,更新答案,否则 $break$,不需要继续判断这个区间的其他的值。
这样的方法时间复杂度是$O(n\sqrt {n})$的。
丑陋的代码:
#include<algorithm> #include<iostream> #include<cstring> #include<cstdio> #define min(x,y) ((x)<(y)?(x):(y)) #define Maxn 1050 #define Reg register using namespace std; long long n,k,ans,C,maxx; long long num[Maxn]; bool judge(long long x,long long P) { long long anp=0; for(Reg int i=1;i<=n;++i) { if(num[i]%x==0) anp+=num[i]/x; else anp+=(num[i]/x+1); if(anp>P) return 0; } if(anp>P) return 0; else return 1; } int main() { long long P; scanf("%lld%lld",&n,&k); C=k; for(Reg int i=1;i<=n;++i) { scanf("%lld",&num[i]); maxx=max(maxx,num[i]); C+=num[i]; } long long l=1,r=1; while(l<=maxx+k) { r=C/(C/l); P=C/l; if(judge(min(r,maxx+k),P)) ans=min(r,maxx+k); l=r+1; } printf("%lld",ans); return 0; }
C. 超级树
$wd$大神博客,传送门。
丑陋的代码:
#include<iostream> #include<cstring> #include<cstdio> #define Maxn 3050 #define max(x,y) ((x)>(y)?(x):(y)) #define Reg register using namespace std; long long sum,n,mod,dp[Maxn][Maxn]; int main() { scanf("%lld%lld",&n,&mod); dp[1][1]=dp[1][0]=1; for(Reg int i=1;i<=n-1;++i) { for(Reg int j=0;j<=n-i+2;++j) { for(Reg int k=0;k<=n-i+2-j;++k) { sum=(dp[i][j]*dp[i][k])%mod; dp[i+1][j+k]=(dp[i+1][j+k]+sum)%mod; //选j+k条路径 左和右分别选的贡献 dp[i+1][j+k+1]=(dp[i+1][j+k+1]+sum)%mod; //选j+k+1条路径 左和右分别选+选根的贡献 dp[i+1][j+k]=(dp[i+1][j+k]+sum*2*(j+k))%mod; //选j+k条路径 左(和右)分别选一条边(起点或终点)连到根的贡献 if(j+k-1>=0) dp[i+1][j+k-1]=(dp[i+1][j+k-1]+sum*j*k*2)%mod; //选j+k-1条路径 左(右)选一条边到根在连右(左)的贡献 if(j+k-1>=0) dp[i+1][j+k-1]=(dp[i+1][j+k-1]+sum*(j*j-j+k*k-k))%mod; //选j+k-1条路径 左(右)连根再连左(右)的贡献 } } } printf("%lld",dp[n][1]%mod); return 0; }
总结
$T1$没想到欧拉回路,先用$tarjan$缩了一个点,
考场上想到的先是$tarjan$缩完点之后变成了一个森林,
对于森林的每个节点,在和它相连的边中选$2$条,然后这样更新,
最后判自环,$2$个自环,$1$个自环$1$条边。
然后树上的叶子节点的环中如果有大于$2$条边,那么它的贡献只有$2$,(当时不知道怎么想的)
最后统计$ans$输出。。
$T2$看到这个题就想到二分。。。
码了一遍二分,样例直接过,觉得自己没错,没想到它没有单调性。。。
最后$20$分(但是别人的二分都是$30$……)
$T3$没什么思路,
先打了一遍$dfs$,跑到了$4$,最后开始跑$5$的点,跑了大概半个小时。。
这样骗到$15$分。。
最后$0+20+15=35$。(第一题直接$cout<<0$可以得$20$而我爆零)
没什么水平。。。