HZOI0727爆零赛
写在前面:
要吃早饭,不然头晕手抖不想写暴力分
考试概况:
T1 : 数学原根优化矩阵BOOST期望DP pts:0
T2 : 树形DP,推式子大题 pts:0
T3 : 简单组数,DP pts:80
sum:80 rank:26
其实T1能打10分,T2能打30分,但是头疼+牙周炎+手抖,打完T3就没心情了
考试流程:
看T1,原根?期望?走人
看T2,树形DP?没学过没写过,还要写DFS?我好懒啊放过我吧
看T3,组数?我好像还记得点,打吧,打点别爆零就行
打了2.5h发现自己好像能吃好多分.....打到typ=3不会,dp走人
然后回去翻了翻前面的题,一个都不想写,头疼睡了
考完试之后听HZ大佬吴迪说自己最后30min快速RUSHT1拿了50pts,瞬间感觉心态挺重要
但是什么都不会心态又有什么用呢
T1:随(rand)
期望绝赞不会中
以前打的期望题没一个能套上去的,我自闭了
下来题解TM看不懂一个字,都是啥啊
考场上看到有个n=1开开心心打个快速幂,但是好像理解错题意连10pts都没有
绝赞极度难过中
T2:单(single)
在套路上玩出新意的好题
有两种情况,第二种相对恶心,先来第一种
首先pick节点1来作为根,然后求出对于每个节点它的子树的点的权值和,
暴力求出根的答案,然后O(n)遍历树上所有点并换根DP,可以推一个简单式子来得到每个点的答案
对于第二种,通过儿子减父亲,可以得到 子树中的点权和 和 树上所有其他点权和 的 差,然后高斯消元
但是蒟蒻刚才去看了隔壁大佬的博客发现好像不用高斯消元,只要大力推式子即可,
下面部分转载自模拟赛9 SDFZ的RANK1 队爷tkj
我们取1号节点为根。
sum表示所有节点a值之和,size表示子树a值之和。
我们考虑从a求b的过程,因为我们原来是换根求的b数组,所以相邻两个节点的b值相减后为sum-2sizey
如果我们能知道sum的话所有值就能求出来了,但是要怎么求sum呢,考场上没想出来QAQ。
总是想着把它们加起来可以搞出sum,结果不行。
我们发现一号节点没有这个式子,所以我们把它的式子暴力写出来
b1=∑ni=1aidepi
我们惊奇的发现这个式子等于∑ni=2sizei
减一下就出来了。
考场上就差最后两行了,Orz
∑ni=1aidepi = ∑ni=2sizei 我跪了,我没想到
看过别人博客之后才发觉自己的博客只有自己能看懂,但强人的博客别人也能看懂,Orz
考场上想到没学过,学过不会背板子,背了细节调不对,人真难啊
#include<bits/stdc++.h> using namespace std; const int MAXN = 100010; long long siz[MAXN],deep[MAXN],a[MAXN],b[MAXN],n,sum; long long lef = 0; struct Edge{ int nxt,to; }edge[MAXN<<1]; int head[MAXN],ectr; void addedge(int from,int to){ edge[++ectr].nxt =head[from]; head[from] = ectr; edge[ectr].to = to; } void dfs1(int x,int fa){ deep[x] = deep[fa] + 1; siz[x] = a[x]; for(int i=head[x];i;i=edge[i].nxt){ int to = edge[i].to; if(to == fa) continue; dfs1(to,x); siz[x] += siz[to]; } } void dfs2(int x,int fa){ b[x] = b[fa] - 2*siz[x] + siz[1]; for(int i=head[x];i;i=edge[i].nxt){ if(edge[i].to == fa) continue; dfs2(edge[i].to,x); } } void dfs3(int x,int fa){ lef += b[x];lef -= b[fa]; for(int i=head[x];i;i=edge[i].nxt){ int to = edge[i].to; if(to == fa) continue; dfs3(to ,x); } } void dfs4(int x,int fa){ siz[x] = (sum + b[fa] - b[x]) / 2; for(int i=head[x];i;i=edge[i].nxt){ int to = edge[i].to; if(to == fa) continue; dfs4(to,x); } } void dfs5(int x,int fa){ a[x] = siz[x]; for(int i=head[x];i;i=edge[i].nxt){ int to = edge[i].to; if(to == fa) continue; a[x] -= siz[to]; dfs5(to,x); } } void solve1(){ siz[1] = a[1]; for(int i=head[1];i;i=edge[i].nxt){ dfs1(edge[i].to,1); siz[1] += siz[edge[i].to]; } for(int i=1;i<=n;i++){ b[1] += a[i] * deep[i]; } for(int i=head[1];i;i=edge[i].nxt){ dfs2(edge[i].to,1); } for(int i=1;i<=n;i++){ cout<<b[i]<<" "; } cout<<endl; return; } void solve2(){ for(int i=head[1];i;i=edge[i].nxt){ int to = edge[i].to; dfs3(to ,1); } lef += 2*b[1]; sum = lef / (n - 1); for(int i=head[1];i;i=edge[i].nxt){ int to = edge[i].to; dfs4(to,1); } long long evil = 0; for(int i=head[1];i;i=edge[i].nxt){ int to = edge[i].to; evil += siz[to]; } a[1] = sum - evil; siz[1] = sum; for(int i=head[1];i;i=edge[i].nxt){ dfs5(edge[i].to,1); } for(int i=1;i<=n;i++){ cout<<a[i]<<" "; }cout<<endl; return; } void _sweep(){ memset(head,0,sizeof head);ectr = 0;lef = 0;sum = 0; memset(edge,0,sizeof edge);memset(a,0,sizeof a);memset(b,0,sizeof b); n=0;memset(siz,0,sizeof siz);memset(deep,0,sizeof deep); } int main(){ ios::sync_with_stdio(false); int T; cin>>T; while(T--){ _sweep(); cin>>n; for(int i=1;i<n;i++){ int a,b; cin>>a>>b; addedge(a,b); addedge(b,a); } int typ; cin>>typ; if(typ == 0){ for(int i=1;i<=n;i++){ cin>>a[i]; } solve1(); } if(typ == 1){ for(int i=1;i<=n;i++){ cin>>b[i]; } solve2(); } } return 0; }
T3:题(problem)
四种情况
第一种:简单组合问题,但莫名其妙不优化式子跑不满25pts,我只有20pts
第二种:一眼卡特兰数,背的exgcd逆元组合数直接往上砸,拿分走人
第三种:观察到数据范围不一样,DP之魂燃烧,写码农代码
第四种:考场上想出来25pts然后自己否了自己,写了个10pts码农DP,后来听说是卡特兰数相乘,悔恨泪水流下来,
wdnmd老子调了一晚上终于A了,要预处理inv和fac
#include<bits/stdc++.h> using namespace std; const int mod = 1000000007; int n,typ; long long invv[200010],fac[200010]; int dpx[2][2100],dpy[2][2100]; long long qp(long long a,long long x){ long long ret = 1; while(x){ if(x&1) ret = (ret * a) % mod; a = (a * a) % mod; x >>= 1; } return ret; } void set_up(long long n){ fac[0] = 1; for(int i=1;i<=n;i++){ fac[i] = fac[i-1] * i % mod; } invv[n]=qp(fac[n],mod-2); for(int i=n-1;i>=0;i--){ invv[i] = invv[i+1] * (i+1) % mod; } return; } long long C(long long n,long long m){ return fac[n] * invv[n-m] % mod * invv[m] % mod; } long long catelan (long long n){ return (C(2*n,n) - C(2*n,n-1) + mod) % mod; } int main(){ ios::sync_with_stdio(false); cin>>n>>typ; set_up(n); if(typ == 0) { long long ans = 0; for(int i=0;i<=n/2;i++) { ans = ans + ((C(n,2*i) * C(2*i,i)) % mod + mod) * C(n-(2*i), (n-2*i)/2) % mod; ans %= mod; } cout<<ans<<endl; return 0; } if(typ == 1) { cout<<(long long)((C(n,n/2) - C(n,(n/2)-1)) + mod) % mod<<endl; return 0; } if(typ == 2){ int del = 1010; dpx[0][del] = 1; dpy[0][del] = 1; for(int x=1;x<=n;x++){ for(int j=1;j<=x;j++){ dpx[x&1][j+del] = dpx[(x-1)&1][j+del+1]; dpx[x&1][j+del] += dpx[(x-1)&1][j+del-1]; dpx[x&1][j+del] %= mod; dpy[x&1][j+del] = dpy[(x-1)&1][j+del+1]; dpy[x&1][j+del] += dpy[(x-1)&1][j+del-1]; dpy[x&1][j+del] %= mod; dpx[x&1][del-j] = dpx[(x-1)&1][del-j+1]; dpx[x&1][del-j] += dpx[(x-1)&1][del-j-1]; dpx[x&1][del-j] %= mod; dpy[x&1][del-j] = dpy[(x-1)&1][del-j+1]; dpy[x&1][del-j] += dpy[(x-1)&1][del-j-1]; dpy[x&1][del-j] %= mod; } dpx[x&1][del] = dpx[(x-1)&1][del-1] + dpx[(x-1)&1][del+1]; dpx[x&1][del] %= mod; dpx[x&1][del] += dpy[(x-1)&1][del-1]; dpx[x&1][del] %= mod; dpx[x&1][del] += dpy[(x-1)&1][del+1]; dpx[x&1][del] %= mod; dpy[x&1][del] = dpx[x&1][del]; } cout<<dpx[n&1][del]<<endl; return 0; } if(typ == 3){ long long ans = 0; for(int i=0;i<=n;i+=2){ ans = (ans + C(n,i)*catelan(i/2)%mod*catelan((n-i)/2)%mod)%mod; } cout<<ans<<endl; return 0; } }
这次爆零获得的经验:
1.对于一些式子特别长的题,在打式子的时候最好调用函数,好调好写,还缩短代码量,美滋滋(参考了tkj码风)
2.耐心推一推,写一写,调一调,就能拿到分的题,还是要好好下功夫的(比如T2),下考了也要练一练
3.进行决策,合理放弃(如T1)。
写在最后:
我想DKYgg了
TAG : SIN_XIII