写在前面:
我是机房透明人
考试概况:
考试风格为思维题,普遍思维难度大而代码量小.
T1 : 图论+答案统计 tps : 0 time : 0.5h
T2 : 线性数学优化 tps : 30 time : 1h
T3 : 树形dp tps : 5 time : 2h
sum : 35 rank33
T1
考场上没想到性质,直接跳了.后来想想也许搞T1能搞出来.
简化题意,把每条边拆成两条,并且这两条的编号相同,要求拆掉两条编号不同的边之后能跑一个欧拉通路,求有多少种不同的拆法.
由于每条边都拆成了两条所以每一个点在刚开始时度数都是偶数,随便跑欧拉回路.
考虑怎么拆能合法,手活样例之后发现只要拆有相同点的两条边就行了;
[实际上,也可以理解为跑一个欧拉回路后后退两步,因为原图上任选一个起点都能跑欧拉回路,所以后退的两步也是随意的,只要这两步是连着的就行了]
用d[]表示点的度数(同一编号不重复计算)
ans += (d[i]-1) * d[i] / 2;
再考虑自环问题,原题说可以存在自环,自环对于能不能跑欧拉通路没有影响,但自环会影响拆边
再手活数据发现可以拆一个环加任意一条正常边,或者是拆两个不同的环
ans += clr * (m - clr) + clr * (clr - 1) / 2
注意除二的问题,同机房两个同学除二出问题导致只有40分
再考虑联通问题,合法的图应该是一张联通图,或者是一张连通图加上一堆没有边的点
不合法的图就是两张或更多的图或者是散点上有自环
比较科学的判法是用并查集,每次merge边的两个端点,自环不连边(因为连了也只是fa[x]=x)
弄完之后再遍历一遍所有的边,查每条边的两个端点是不是在同一个集里面
发现由于刚开始merge了每条边的两个点所以查每条边的一个点就行了
鬼数据不全开long long不给过(什么东西)
#include<bits/stdc++.h> using namespace std ; const long long MAXN = 100010; long long a[MAXN],b[MAXN],fa[MAXN],d[MAXN],n,m; long long ans,crl; long long Find(long long x){ return (fa[x] == x) ? x : fa[x] = Find(fa[x]); } void Merge(long long x,long long y){ long long xx = Find(x),yy = Find(y); fa[xx] = yy; } signed main(){ ios::sync_with_stdio(false); cin>>n>>m; for(long long i=1;i<=n;i++){ fa[i] = i; } for(long long i=1;i<=m;i++){ cin>>a[i]>>b[i]; if(a[i]==b[i]){ crl ++; } else { ++d[a[i]]; ++d[b[i]]; } } long long FUCK_YOU = Find(a[1]);// for(long long i=2;i<=m;i++){ long long NMSL = Find(a[i]); if(NMSL != FUCK_YOU){ cout<<"0"<<endl; return 0; } } for(long long i=1;i<=n;i++){ ans += (d[i] * (d[i]-1)) /2; } ans += crl * ((long long)m - crl); ans += crl * (crl-1) /2; cout<<ans<<endl; return 0; }
T2 : 砍树
不会,我滚
考场上打了个暴力 30tps
T3 : 超级树
一个人疯狂的爱上某一个人,某一件事,某一个物,终究爱的只是脑海里的那种执念.
考场上想的深度还是不够,或者说是想偏了
首先有一个非常明显的做法,一棵k层的超级树可以由两棵k-1的超级树上的每一个点都向一个额外的点连边而构成
形象点就是两棵同样大小的树拼起来就能成一棵更大的树;
那么考虑设计状态为dp[i]为有k层的超级树具有的答案,由dp[i-1]向dp[i]转移
分为4种情况:
1 . 这条路径不过根节点,经过一棵子树
2 . 这条路径过根节点,经过一棵子树
3 . 这条路径过根节点,经过两颗子树
4 . 这条路径经过根节点,不经过子树
对于1,分别有左右两棵子树 dp[i] += dp[i-1] * 2
对于3,每条dp[i-1]里的每条路径末端都可以连向根节点,另一棵子树也是如此,那么考虑顺序(乘二) dp[i] += dp[i-1] * dp[i-1] * 2
对于4,dp[i] += 1
对于2,无法统计.(我以考场上的2h庄严宣判无法统计)
很容易发现对于在一棵超级树上加一个点这种操作来说,可以与原来路径的头或者尾相连,但是同时他也可以作为连接两条路径中间的点,
而这种统计是现有的状态无法做到的,我们需要加另一个状态来让他可以支持这种统计。
但是这个状态很难想,观察我们的无法维护的点是将几条路径连起来,那么我们可以设置另一个状态为有j条不交路径的方案数有dp[i][j]种
边界为dp[1][0] = dp[1][1] = 1,目标为dp[n][1]
例如dp[2][0] = 1,dp[2][1] = 9,dp[2][2] = 7,dp[2][3]=1;
然后就可以开始写方程了:
1.新点不连边并且不加入统计 dp[i][l+r] += dp[i-1][l] * dp[i-1][r]
2.新点不连边并且加入统计 dp[i][l+r+1] += dp[i-1][l] * dp[i-1][r]
3.新点连边,与左右子树中任意一条路径的首尾分别相连dp[i][l+r] += dp[i-1][l] * dp[i-1][r] * (l+r) * 2
4.新点连边,与子树中两条路径相连(选定两条路径后有两种连法),dp[i][l+r-1] += dp[i-1][l] *dp[i-1][r] * (l+r) * (l+r-1) / 2 * 2
写完之后开始观察数据范围,k<=300,而第二维的上限看上去是2^300的,难道这种设计不可取吗?
注意观察dp方程,第二维每次推动时最多减少1,而我们的目标是dp[n][1]
那么对于每一层来说,能影响到dp[n][1]的最远第二维下标就是dp[i][n-i+1]
所以每次循环到n-i+1就行了,但发现这种写法样例都过不了,why?
回头看n-i+1的地方,计算机每次都处理到整数部分,所以那个看上去像y=-x的方程应该是向外的阶梯型函数,
试着把循环上界变为n-i+2,发现就能过了。
奇怪的是这个代码只能过95分(全开longlong还挂)
#include<bits/stdc++.h> using namespace std; const int MAXN = 410; long long mod, n; long long dp[MAXN][MAXN]; long long DP(){ dp[0][1] = 1; dp[1][0] = dp[1][1] = 1; for(int i=2;i<=n;i++){ for(int l=0;l<=n;l++){ for(int r=0;r<=n;r++){ long long num = dp[i-1][l] * dp[i-1][r] % mod; if(l+r <= n-i+2){ dp[i][l+r] += num; dp[i][l+r] %= mod; dp[i][l+r] += num * (l+r) * 2; dp[i][l+r] %= mod; } if(l+r+1 <= n-i+2){ dp[i][l+r+1] += num; dp[i][l+r+1] %= mod; } if(l+r-1 <= n-i+2){ dp[i][l+r-1] += num * (l+r) * (l+r-1); dp[i][l+r-1] %= mod; } } } } return dp[n][1]; } int main(){ ios::sync_with_stdio(false); cin>>n>>mod; cout<<DP()<<endl; return 0; }
TAG : SIN_XIII
因为Linux打不出来圈九了QAQ