T1:
一道十分奇妙的人类智慧题......
二次函数(求导)易证题面中给的那个操作就是求算术平均值......
然后我发现每次操作相当于把坐标对答案的贡献乘以1/k,然后就失去梦想写暴力了......
考虑这个暴力怎么写,任凭实数炸精度显然是不行的,我们可以在mod 998244353下计算坐标,把最终答案都丢进一个set里面,最后输出它的size。
我们在dfs的时候可以用vector传递一个状态,在dfs中next_permutation枚举下一个状态(钦定前k个进行合并)。
由于这么枚举状态会dfs到重复状态,所以我们要对表示状态vector再次哈希判重。
(话说这又是vector又是set又是哈希套哈希的居然还能有15分)
考虑正解,显然我们可以把合并操作建立成一棵满k叉树。
对于同一层的所有点,我们可以排序;对于一个非叶子节点,如果它的孩子的颜色全部相同,那么我们可以删去它的孩子把自己变成一个和孩子颜色相同的点。
通过这样的操作,我们一定能把一棵满k叉树变成每一层只有k-1个叶子和一个中间节点(最深层k个叶子)的形态,并且由于深度+1使得贡献乘以1/k,这种树一定一一对应一个最终位置(虽然不一定合法)。
如果我们设树的深度为x,黑点总数为y,那么这样的树的个数就是一个经典的数位DP问题(长度为x的k进制数,数字和为y)。
但是这样存在后缀零,计算答案时去掉后缀零的话,我们让dp[x][y]减去dp[x-1][y]就好了。
考虑这样的树什么情况下合法,我们需要把它还原回去,计算黑色节点总数和白色节点总数。
显然一次缩点操作减少k-1个节点,所以如果n-黑色点总数或者m-白色点总数取模k-1不为0的话,肯定不行。
然后就能AC啦。
15分暴力代码:
1 #include<bits/stdc++.h> 2 #define debug cout 3 using namespace std; 4 typedef long long int lli; 5 const int mod=998244353; 6 7 inline lli fastpow(lli base,int tim) { 8 lli ret = 1; 9 while(tim) { 10 if( tim & 1 ) ret = ret * base % mod; 11 if( tim >>= 1 ) base = base * base % mod; 12 } 13 return ret; 14 } 15 16 set<unsigned long long int> vis; 17 set<int> st; 18 19 int n,m,k,inv; 20 21 inline bool judge(const vector<int> &v) { 22 unsigned long long int hsh = 0; 23 for(unsigned i=0;i<v.size();i++) hsh = hsh * 27 + ( v[i] + 1 ); 24 if( vis.find(hsh) != vis.end() ) return 1; // found; 25 return vis.insert(hsh),0; 26 } 27 inline void dfs(vector<int> v) { 28 if( v.size() == 1 ) return void(st.insert(*v.begin())); 29 sort(v.begin(),v.end()); 30 if( judge(v) ) return; 31 do { 32 lli su = 0; 33 for(int i=0;i<k;i++) su += v[i]; 34 su = su * inv % mod; 35 vector<int> nxt; 36 for(unsigned i=k;i<v.size();i++) nxt.push_back(v[i]); 37 nxt.push_back(su) , dfs(nxt); 38 } while( next_permutation(v.begin(),v.end())); 39 } 40 41 int main() { 42 scanf("%d%d%d",&n,&m,&k) , inv = fastpow(k,mod-2); 43 if( !n || !m ) return puts("1") , fclose(stdout); 44 vector<int> v; 45 for(int i=1;i<=n;i++) v.push_back(0); 46 for(int i=1;i<=m;i++) v.push_back(1); 47 dfs(v) , printf("%d ",(int)st.size()); 48 return 0; 49 }
正解代码:
1 #include<cstdio> 2 typedef long long int lli; 3 const int maxn=2e3+1e2; 4 const int mod=1e9+7; 5 6 int f[maxn<<1][maxn<<1]; 7 int n,m,k,t,ans; 8 9 inline int sub(const int &a,const int &b) { 10 register int ret = a - b; 11 return ret < 0 ? ret + mod : ret; 12 } 13 inline void adde(int &a,const int &b) { 14 if( ( a += b ) >= mod ) a -= mod; 15 } 16 17 inline void getf() { 18 f[0][0] = 1; 19 for(int i=1;i<=t;i++) // t * k = n + m so these fors are O(n^2) 20 for(int su=0;su<=n;su++) 21 for(int ths=0;ths<k&&ths<=su;ths++) 22 adde(f[i][su],f[i-1][su-ths]); 23 } 24 inline void calc() { 25 for(int len=1;len<=t;len++) 26 for(int su=0;su<=n;su++) { 27 const int rel = sub(f[len][su],f[len-1][su]); // cut trailing zeros . 28 const int wite = len * ( k - 1) + 1 - su; // calc white points . 29 if( wite <= 0 || wite > m || ( n - su ) % ( k - 1 ) || ( m - wite ) % ( k - 1 ) ) continue; // illegal . 30 adde(ans,rel); 31 } 32 } 33 34 int main() { 35 scanf("%d%d%d",&n,&m,&k) , t = ( n + m - 1 ) / ( k - 1 ); 36 if( !n || !m ) return puts("1"),0; 37 getf() , calc() , printf("%d ",ans); 38 return 0; 39 }
T2:
这看起来是猫(给)锟的题啊......
不过应该把加强两个字去掉,这就是北京八十中集训的原题。
显然我们每条边都要走一遍......
考虑一个初始情况,我们不加任何边,那么,树上的每条边我们都要走两遍,相当于在正常走一遍的情况下再把树遍历一遍。
所以我们可以减去边权总和,这样我们要计算的就是通过连接不超过k条边权为v的边,把树遍历一遍的的最小代价。
如果我们先不考虑不超过k条这个限制,仅考虑代价最小的话,显然连接的边数是关于v单调递减的。
所以我们可以二分这个v,找到一个需要的边数恰好<=k的v,然后用现在的边数和原来的v计算答案。
现在我们知道了v,怎么计算最小代价和需要的边数呢(显然我们在代价相同的时候要让增加的边尽可能的小)?
这是一个很经典的DP问题,f[i][0/1]表示遍历i的子树,i这个点有无新加边的接头的最小代价及边数,转移手玩一下就好了。
初始化f[i][0]=(0,0),f[i][1]=(v,1),转移就是
f[i][0]=min{f[i][0]+f[son][0]+(len,0),f[i][1]+f[son][1]-(v,1),f[i][1]+f[son][0]+(len,0),f[i][0]+f[son][1]},
f[i][1]=min{f[i][0]+f[son][0]+(len,0)+(v,1),f[i][1]+f[son][1],f[i][1]+f[son][0]+(len,0),f[i][0]+f[son][1]};
的说。
统计答案的时候注意在使用我们二分的v计算时,需要的边数可能比k小,但是我们一定要强制替换k条边,
因为我们二分的v一定比给定的v大,这样相当于把一些和大v等价的东西替换成了小v,一定更优。否则会WA。
代码:
1 #pragma GCC optimize("Ofast","no-stack-protector") 2 #pragma GCC target("avx") 3 #include<cstdio> 4 #include<algorithm> 5 #include<cctype> 6 typedef long long int lli; 7 const int maxn=3e5+1e2; 8 9 struct Node { 10 lli cst,tim; 11 friend Node operator + (const Node &a,const Node &b) { return (Node){a.cst+b.cst,a.tim+b.tim}; } 12 friend Node operator - (const Node &a,const Node &b) { return (Node){a.cst-b.cst,a.tim-b.tim}; } 13 friend bool operator < (const Node &a,const Node &b) { return a.cst != b.cst ? a.cst < b.cst : a.tim < b.tim; } 14 }f[maxn][2],per; 15 16 int s[maxn],t[maxn<<1],nxt[maxn<<1],l[maxn<<1]; 17 int n,k,c; 18 lli su; 19 20 __inline void coredge(int from,int to,int len) { 21 static int cnt; 22 t[++cnt] = to , l[cnt] = len , nxt[cnt] = s[from] , s[from] = cnt; 23 } 24 __inline void addedge(int a,int b,int l) { 25 coredge(a,b,l) , coredge(b,a,l); 26 } 27 28 __inline void dfs(int pos,int fa) { 29 f[pos][0] = (Node){0,0} , f[pos][1] = per; 30 for(int at=s[pos];at;at=nxt[at]) if( t[at] != fa ) { 31 dfs(t[at],pos); 32 const Node w = (Node){l[at],0}; 33 const Node tp0 = std::min( f[pos][1] + f[t[at]][1] - per , f[pos][0] + f[t[at]][0] + w ); 34 const Node tp1 = std::min( f[pos][1] + f[t[at]][0] + w , f[pos][0] + f[t[at]][1] ); 35 f[pos][0] = std::min( tp0 , tp1 ) , f[pos][1] = std::min( tp0 + per , tp1 ); 36 } 37 } 38 __inline Node calc(lli cst) { 39 per = (Node){cst,1} , dfs(1,-1); 40 return f[1][0]; 41 } 42 __inline lli bin() { 43 Node now; 44 if( (now = calc(c)).tim <= k ) return su + now.cst; 45 lli l = 0 , r = 1e12 , mid; 46 while( r > l + 1 ) { 47 mid = ( l + r ) >> 1; 48 if( calc(mid).tim <= k ) r = mid; 49 else l = mid; 50 } 51 now = calc(r); 52 return su + now.cst - k * ( r - c ); 53 } 54 55 __inline char nextchar() { 56 static const int BS = 1 <<21; 57 static char buf[BS],*st,*ed; 58 if( st == ed ) ed = buf + fread(st=buf,1,BS,stdin); 59 return st == ed ? -1 : *st++; 60 } 61 __inline int getint() { 62 int ret = 0 , ch; 63 while( !isdigit(ch=nextchar()) ); 64 do ret = ret * 10 + ch - '0'; while( isdigit(ch=nextchar()) ); 65 return ret; 66 } 67 68 int main() { 69 n = getint() , k = getint() , c = getint(); 70 for(int i=1,a,b,l;i<n;i++) a = getint() , b = getint() , su += ( l = getint() ) , addedge(a,b,l); 71 printf("%lld ",bin()); 72 return 0; 73 }
T3:
显然我们能枚举每一条边并计算贡献,就是边权乘以包含这条边的方案数,也就是(C(n,k)-C(siz1,k)-C(siz2,k))*len了。
(别忘了最后乘逆元)
然后我失去梦想就写了60分n^2暴力......
但是,我们观察这个答案的形式非常优美是吧,根本不需要n^2的!
先把C(n,k)*len扔掉,这就是一个组合数乘以边权之和。
于是答案成了-C(t,k)*len的形式,就是-len*t!/k!(t-k)!,我们可以把-1/k!先提出来。
然后我们要卷积的就是,len*t!/(t-k)!,我们把后面的东西翻转一下(把(t-k)!放到位置n-(t-k)),于是关于k的答案就会被卷积到位置n+k了,之后随便做就好啦。
明明是这么显然的卷积,考场上为什么推不出来啊!!!
(果然还是太菜了呢)
60分暴力代码:
1 #include<cstdio> 2 typedef long long int lli; 3 const int maxn=2e5+1e2; 4 const int mod=998244353; 5 6 lli fac[maxn],inv[maxn],ans,su; 7 int n; 8 9 inline lli c(int n,int m) { 10 if( n < m ) return 0; 11 return fac[n] * inv[m] % mod * inv[n-m] % mod; 12 } 13 inline lli fastpow(lli base,int tim) { 14 lli ret = 1; 15 while(tim) { 16 if( tim & 1 ) ret = ret * base % mod; 17 if( tim >>= 1 ) base = base * base % mod; 18 } 19 return ret; 20 } 21 inline void init() { 22 *fac = 1; for(int i=1;i<=n;i++) fac[i] = fac[i-1] * i % mod; 23 inv[n] = fastpow(fac[n],mod-2); for(int i=n;i;i--) inv[i-1] = inv[i] * i % mod; 24 } 25 26 namespace Tree { 27 int s[maxn],t[maxn<<1],nxt[maxn<<1],l[maxn<<1],siz[maxn]; 28 inline void coredge(int from,int to,int len) { 29 static int cnt; 30 t[++cnt] = to , l[cnt] = len , nxt[cnt] = s[from] , s[from] = cnt; 31 } 32 inline void addedge(int a,int b,int l) { 33 coredge(a,b,l) , coredge(b,a,l); 34 } 35 inline void pre(int pos,int fa) { 36 siz[pos] = 1; 37 for(int at=s[pos];at;at=nxt[at]) if( t[at] != fa ) pre(t[at],pos) , siz[pos] += siz[t[at]]; 38 } 39 inline void dfs(int pos,int fa,int k) { 40 for(int at=s[pos];at;at=nxt[at]) if( t[at] != fa ) { 41 ans = ( ans + ( ( su - c(siz[t[at]],k) - c(n-siz[t[at]],k) ) % mod + mod ) % mod * l[at] % mod ) % mod; 42 dfs(t[at],pos,k); 43 } 44 } 45 } 46 47 int main() { 48 static int q; 49 scanf("%d%d",&n,&q) , init(); 50 for(int i=1,a,b,l;i<n;i++) scanf("%d%d%d",&a,&b,&l) , Tree::addedge(a,b,l<<1); 51 Tree::pre(1,-1); 52 for(int i=1,k;i<=q;i++) { 53 scanf("%d",&k) , ans = 0 , su = c(n,k); 54 Tree::dfs(1,-1,k) , printf("%lld ",ans*fastpow(su,mod-2)%mod); 55 } 56 return 0; 57 }
正解代码:
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<algorithm> 5 #define debug cout 6 typedef long long int lli; 7 using namespace std; 8 const int maxn=524289; 9 const int mod=998244353,g=3; 10 11 int fac[maxn],inv[maxn],a[maxn],b[maxn],ans[maxn]; 12 13 inline int add(const int &a,const int &b) { 14 register int ret = a + b; 15 return ret >= mod ? ret - mod : ret; 16 } 17 inline int sub(const int &a,const int &b) { 18 register int ret = a - b; 19 return ret < 0 ? ret + mod : ret; 20 } 21 inline int mul(const int &a,const int &b) { 22 return (lli) a * b % mod; 23 } 24 inline void adde(int &a,const int &b) { 25 if( ( a += b ) >= mod ) a -= mod; 26 } 27 28 inline int fastpow(int base,int tim) { 29 int ret = 1; 30 while(tim) { 31 if( tim & 1 ) ret = mul(ret,base); 32 if( tim >>= 1 ) base = mul(base,base); 33 } 34 return ret; 35 } 36 inline void NTT(int* dst,int n,int tpe) { 37 for(int i=0,j=0;i<n;i++) { 38 if( i < j ) std::swap(dst[i],dst[j]); 39 for(int k=n>>1;(j^=k)<k;k>>=1); 40 } 41 for(int len=2,h=1;len<=n;len<<=1,h<<=1) { 42 int per = fastpow(g,mod/len); 43 if( !~tpe ) per = fastpow(per,mod-2); 44 for(int st=0;st<n;st+=len) { 45 int w = 1; 46 for(int pos=0;pos<h;pos++) { 47 const int u = dst[st+pos] , v = mul(dst[st+pos+h],w); 48 dst[st+pos] = add(u,v) , dst[st+pos+h] = sub(u,v) , w = mul(w,per); 49 } 50 } 51 } 52 if( !~tpe ) { 53 const int inv = fastpow(n,mod-2); 54 for(int i=0;i<n;i++) dst[i] = mul(dst[i],inv); 55 } 56 } 57 58 int s[maxn],t[maxn<<1],nxt[maxn<<1],l[maxn<<1],siz[maxn]; 59 int n,q,su; 60 61 inline void coredge(int from,int to,int len) { 62 static int cnt; 63 t[++cnt] = to , l[cnt] = len , nxt[cnt] = s[from] , s[from] = cnt; 64 } 65 inline void addedge(int a,int b,int l) { 66 coredge(a,b,l) , coredge(b,a,l); 67 } 68 inline void dfs(int pos,int fa) { 69 siz[pos] = 1; 70 for(int at=s[pos];at;at=nxt[at]) if( t[at] != fa ) { 71 dfs(t[at],pos) , siz[pos] += siz[t[at]]; 72 adde(a[siz[t[at]]],mul(fac[siz[t[at]]],l[at])) , adde(a[n-siz[t[at]]],mul(fac[n-siz[t[at]]],l[at])); 73 } 74 } 75 76 inline void preans() { 77 int len; 78 for(len=1;len<=n<<1;len<<=1); 79 for(int i=0;i<=n;i++) b[n-i] = inv[i]; 80 NTT(a,len,1) , NTT(b,len,1); 81 for(int i=0;i<len;i++) ans[i] = mul(a[i],b[i]); 82 NTT(ans,len,-1); 83 } 84 inline int c(int n,int m) { 85 return mul(fac[n],mul(inv[m],inv[n-m])); 86 } 87 inline int getans(int k) { 88 if( k == 1 ) return 0; 89 int fs = c(n,k) , sb = mul(ans[n+k],inv[k]) , sum = mul(fs,su); 90 return mul(sub(sum,sb),fastpow(fs,mod-2)); 91 } 92 93 inline void init() { 94 *fac = 1; 95 for(int i=1;i<=n;i++) fac[i] = mul(fac[i-1],i); 96 inv[n] = fastpow(fac[n],mod-2); 97 for(int i=n;i;i--) inv[i-1] = mul(inv[i],i); 98 } 99 100 int main() { 101 scanf("%d%d",&n,&q) , init(); 102 for(int i=1,a,b,l;i<n;i++) scanf("%d%d%d",&a,&b,&l) , addedge(a,b,l<<1) , adde(su,l<<1); 103 dfs(1,-1) , preans(); 104 for(int i=1,k;i<=q;i++) scanf("%d",&k) , printf("%d ",getans(k)); 105 return 0; 106 }
虽然因为中文文件夹名爆了一发零(这个真不赖我),但是这次170(如果不爆栈是175)的成绩也是rank1了。
THU夏令营初审也通过了,大概又要去被虐了呢(没学上.jpg)。
(我果然还是太菜了呢)
あのとき描(えが)いた 約束(やくそく)の場所(ばしょ)で
用心去描绘那一天 在这约定中的地点
きっと いつまでも 待(ま)ってるよ
无论 何时都会在 这里等待
笑顔(えがお)で 抱(だ)きしめるから
带着笑颜,相拥着回首从前
いくつもの 涙(なみだ)の意味(いみ)
究竟眼泪它代表多少意义
忘(わす)れてた気持(きも)ち 教(おし)えてくれたね
请再一次让我感受 早已忘却了的感情
移(うつ)りゆく景色(けしき)の中(なか)で
漫步着去铭记童话般的美景
同(おな)じ道(みち)を歩(ある)いてきた
在同一条路上我们相伴前行
差(さ)し出(だ)した手(て)に 思(おも)い出(で)と
向彼此伸出双手 我终于确定
ボクらの選(えら)んだ(大切(たいせつ)な)
选择了从今天起(最重要的是)
これからが
去奔向黎明