星际旅行
有n个行星由m个双向虫洞连接,不同行星间最多有1个虫洞直接相连,但一个虫洞的两端可能连接同一行星。一条星际旅行的航线满足:从任意一颗行星出发,在任意一颗行星上结束,总共经过m-2个虫洞恰2次,经过两个虫洞恰1次。两条航线不同,当且仅当至少存在一个虫洞在两条航线中经过次数不同。询问本质不同的航线有多少条。
1<=n,m<=1e5
题解
先不考虑自环,如果将边拆成两条,每个点的度数都是偶数,相当于删去两条之后能够走完所有边,这貌似就是欧拉路径,所以需要2个奇数度数的点,那么删去的边一定有且只有一个公共点,不然有4个奇数度数的点或者没有。
那么就可以找到一种答案:枚举每个点作为公共点,那么就需要选出两条边删去,方案数就是$\sum_{i=1}^{n} c(du[i],2) $,du[i]是i在原图上的度数(不算自环);
现在考虑自环,他可以作为删去的边,因为走一遍位置没变没有影响,另外一条删去的边如果也是自环的话,又有一种答案就是c(num2,2),num2为自环个数;如果另外一条边是普通边的话,在2倍边的图上随便删一条边都是欧拉路径(图上边是普通边),方案就是从技术点出发,到达自环时走一遍就继续走欧拉路径,所以又有一种答案就是 num1*num2,num1是普通边个数。
如果自环不作删去的边也没有影响,路过时走两边就好。
原图也可能不连通,不过是对于边来说,因为即使有孤点也不影响。对于边的联通判断还是可以用并查集合并点,判断就看所有边是不是在一个并查集即可。

#include<bits/stdc++.h> using namespace std; #define ll long long const int maxn=100005; int n,m,num1,num2; int fa[maxn],du[maxn]; int xx[maxn],yy[maxn]; ll ans; int find(int x){ return fa[x]==x ? x : fa[x]=find(fa[x]); } int main(){ freopen("tour.in","r",stdin); freopen("tour.out","w",stdout); scanf("%d%d",&n,&m); for(int i=1;i<=n;i++) fa[i]=i; for(int i=1;i<=m;i++){ int x,y; scanf("%d%d",&xx[i],&yy[i]); if(xx[i]==yy[i]) {num2++;continue;} num1++; du[xx[i]]++;du[yy[i]]++; int dx=find(xx[i]),dy=find(yy[i]); if(dx!=dy) fa[dx]=dy; } bool opt=false; int ff=find(xx[1]); for(int i=2;i<=m;i++) if(find(xx[i])!=ff){ opt=true; break; } if(opt){printf("0");return 0;} for(int i=1;i<=n;i++) ans+=(ll)du[i]*(du[i]-1)/2; ans+=(ll)num1*num2+(ll)num2*(num2-1)/2; printf("%lld",ans); }
砍树
林先森有n颗树苗,重在一条直线上。初始时所有树苗高度为0,每过一天长高1米。对于每颗树苗,林先森询问他的最终高度为ai,因此他会定时检查树苗的情况,并及时砍掉过高的树苗。具体来说,从种下树苗每d天林先森会检查一遍树苗,如果有树苗不低于他希望的高度,林先森会把高出的部分(可以为0)砍掉,之后这颗树苗不再长高。林先森希望砍掉的树苗的总长度不超过k米,林先森希望知道最大可能的d。
1<=n<=100,0<=k<=1e11,1<=ai<=1e9
题解
可以得到$\sum_{i=1}^{n}\left \lceil \frac{a_{i}}{d} \right \rceil*d-a_{i}<=k$
设$sum=k+\sum_{i=1}^{n}a_{i}$
那么$\sum_{i=1}^{n}\left \lceil \frac{a_{i}}{d} \right \rceil*d<=sum$
对于每个$a_{i}$,$\left \lceil \frac{a_{i}}{d} \right \rceil$,有$O(\sqrt{a_{i}}})$个,
因此$\sum_{i=1}^{n}\left \lceil \frac{a_{i}}{d} \right \rceil$只有$O(n*\sqrt{a_{i}})$种不同取法(d有这么多种取法)
那么所有的d的取值去重排序后,然后暴力计算,检验是否求出来的d是否在这个取值所要求的d的范围内,并更新答案即可。
(照着题解说,有些地方还不懂)

#include<bits/stdc++.h> using namespace std; #define ll long long const int maxn=40005; int n,cnt; ll a[105],ans,k; ll d[maxn*200]; int main(){ freopen("cut.in","r",stdin); freopen("cut.out","w",stdout); scanf("%d%lld",&n,&k); for(int i=1;i<=n;i++){ scanf("%lld",&a[i]); k+=a[i]; } for(int i=1;i<=n;i++) for(int j=1;j*j<=a[i];j++){ d[++cnt]=j; d[++cnt]=(a[i]-1)/j+1; } sort(d+1,d+cnt+1); cnt=unique(d+1,d+cnt+1)-d-1; for(int i=1;i<=cnt;i++){ ll ret=0; for(int j=1;j<=n;j++) ret+=(a[j]-1)/d[i]+1; if(d[i]<=k/ret) ans=k/ret;//????? } printf("%lld",ans); }
超级树
一个k-超级树可按如下方法得到:取一颗深度为k的满二叉树,对每个节点,向他的所有祖先连边(如果改边不存在),如
先统计一颗k-的超级树有多少条每个节点最多经过一次的不同有向路径。两条路径被认为不同,当且仅当它们经过的节点集合不同或者经过节点顺序不同。答案对mod取模
1<=k<=300,1<=mod<=1e9
题解
直接说做法了(反正我想不出)。
f[i][j]为i-超级树内选j条点不重复的路径方案数。
分5类,考虑递推,枚举左右子树选择条数l,r:
num=f[i-1][l]*f[i-1][r],两者互不影响所以乘法原理
1.不添加路径,直接继承f[i][l+r]+=num
2.根节点单独成为一条路径,f[i][l+r+1]+=num
3.根连接到左子树或右子树中一条路径,f[i][l+r]+=2*(l+r)*num
4.在左右子树分别选一条路径,用根连接,f[i][l+r-1]+=2*l*r*num
5.在左子树或右子树选2条路径,用根连接,f[i][l+r-1]+=num*(l*(l-1)+r(r-1))
解析代码里面有

#include<bits/stdc++.h> using namespace std; #define ll long long int n,mod; ll f[305][1025];//深度为i的超级树点不重复路径(有向)的条数为j时的方案数 int main(){ freopen("tree.in","r",stdin); freopen("tree.out","w",stdout); scanf("%d%d",&n,&mod); f[1][0]=f[1][1]=1; for(int i=2;i<=n;i++){ int lim; if(i<=9) lim=(1<<i)-1; else lim=n-i+2; for(int l=0;l<=lim;l++) for(int r=0;l+r<=lim;r++){ ll num=f[i-1][l]*f[i-1][r]%mod; f[i][l+r]+=num;//什么都不做 f[i][l+r]+=2*(l+r)*num%mod;//根连接到左子树或者右子树,2是因为路径有向,所以连接到两端不一样 f[i][l+r+1]+=num;//根自己作为一条路径 f[i][l+r]%=mod; f[i][l+r+1]%=mod; if(l+r){ f[i][l+r-1]+=2*num*l*r%mod;//在左右两颗子树分别选一条路径用根连接,连接之后两条变成一条,所以l+r-1,2是因为可以从左子树到右子树也可以右子树到左子树 f[i][l+r-1]+=num*(l*(l-1)+r*(r-1))%mod;//在左子树或右子树选两条路径,l+r-1同上,本身用根有2(同上)但是与c(x,2)的/2抵消了 f[i][l+r-1]%=mod; } } } printf("%lld",f[n][1]%mod); }
顶不住.....