星际旅行
有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);
}
顶不住.....
