20180528小测

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\n",(int)st.size());
48     return 0;
49 }
View Code

正解代码:

 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\n",ans);
38     return 0;
39 }
View Code

 


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\n",bin());
72     return 0;
73 }
View Code

 


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\n",ans*fastpow(su,mod-2)%mod);
55     }
56     return 0;
57 }
View Code

正解代码:

  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\n",getans(k));
105     return 0;
106 }
View Code

 

 

虽然因为中文文件夹名爆了一发零(这个真不赖我),但是这次170(如果不爆栈是175)的成绩也是rank1了。
THU夏令营初审也通过了,大概又要去被虐了呢(没学上.jpg)。
(我果然还是太菜了呢)

あのとき描(えが)いた 約束(やくそく)の場所(ばしょ)で
用心去描绘那一天 在这约定中的地点
きっと いつまでも 待(ま)ってるよ
无论 何时都会在 这里等待
笑顔(えがお)で 抱(だ)きしめるから
带着笑颜,相拥着回首从前
いくつもの 涙(なみだ)の意味(いみ)
究竟眼泪它代表多少意义
忘(わす)れてた気持(きも)ち 教(おし)えてくれたね
请再一次让我感受 早已忘却了的感情
移(うつ)りゆく景色(けしき)の中(なか)で
漫步着去铭记童话般的美景
同(おな)じ道(みち)を歩(ある)いてきた
在同一条路上我们相伴前行
差(さ)し出(だ)した手(て)に 思(おも)い出(で)と
向彼此伸出双手 我终于确定
ボクらの選(えら)んだ(大切(たいせつ)な)
选择了从今天起(最重要的是)
これからが
去奔向黎明

posted @ 2018-05-28 17:30  Cmd2001  阅读(297)  评论(0编辑  收藏  举报