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

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

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

  • 相关阅读:
    Golang中使用set
    go 删除数组元素
    golang slice 简单排序
    WSGI 配置禁止反向DNS查找
    OpenStack Restful API框架介绍
    kubebuilder controller 资料学习
    package controllerutil
    JavaWeb开发好资料
    Hibernate3.6中文手册
    软件项目版本号的命名规则及格式
  • 原文地址:https://www.cnblogs.com/Cmd2001/p/9101248.html
Copyright © 2020-2023  润新知