5.13训练的一些题解

hdu5307

一般来说一类要求满足整数等式关系才进行答案贡献的问题

没有思路的话可以考虑生成函数转化

题解是构造级数(∑i*x^si)(∑x^(-si-1))-(∑x^si)(∑(i-1)x^(-si-1)

则x^S次方前面的系数就正好是(i-j+1),而这个式子是可以用fft快速展开的,非常巧妙

另外要注意的是,次数可能是负数所以都可以整体+Sn

同时S=0的时候需要单独考虑

还有做ftt的时候手写复数操作会比直接用complex快不少

  1 #include<bits/stdc++.h>
  2 
  3 using namespace std;
  4 typedef long long ll;
  5 typedef long double ld;
  6 const ld pi=acos(-1);
  7 struct po{
  8     ld x,y;
  9     friend po operator +(po a,po b)
 10     {
 11         return (po){a.x+b.x,a.y+b.y};
 12     }
 13     friend po operator -(po a,po b)
 14     {
 15         return (po){a.x-b.x,a.y-b.y};
 16     }
 17     friend po operator *(po a,po b)
 18     {
 19         return (po){a.x*b.x-a.y*b.y,a.x*b.y+a.y*b.x};
 20     }
 21     void init()
 22     {
 23         x=y=0;
 24     }
 25 } a[400010],b[400010];
 26 ld ans[100010];
 27 int s[100010],r[400010],n;
 28 
 29 void fft(po *a,int n,int f)
 30 {
 31     for (int i=0; i<n; i++)
 32         if (i<r[i]) swap(a[i],a[r[i]]);
 33     for (int i=1; i<n; i<<=1)
 34     {
 35         po p=(po){cos(pi/i),sin(pi/i)*f};
 36         for (int j=0; j<n; j+=i<<1)
 37         {
 38             po w=(po){1,0};
 39             for (int k=0; k<i; k++)
 40             {
 41                 po u=a[j+k],v=w*a[j+k+i];
 42                 a[j+k]=u+v;
 43                 a[j+k+i]=u-v;
 44                 w=w*p;
 45             }
 46         }
 47     }
 48 }
 49 
 50 void calc(int m,int f)
 51 {
 52     int n=1,l=0;
 53     for (; n<=m*2; n<<=1) l++;
 54     for (int i=0; i<n; i++)
 55         r[i]=(r[i>>1]>>1)|((i&1)<<(l-1));
 56     fft(a,n,1); fft(b,n,1);
 57     for (int i=0; i<=n; i++) a[i]=a[i]*b[i];
 58     fft(a,n,-1);
 59     for (int i=1; i<=m; i++)
 60         ans[i]+=a[i+m].x/n*f;
 61     for (int i=0; i<=n; i++)
 62     {
 63          a[i].init();
 64          b[i].init();
 65     }
 66 }
 67 
 68 int main()
 69 {
 70     int cas;
 71     scanf("%d",&cas);
 72     while (cas--)
 73     {
 74         scanf("%d",&n);
 75         s[0]=0; ll s0=0,c=0;
 76         for (int i=1; i<=n; i++)
 77         {
 78             int x;
 79             scanf("%d",&x);
 80             s[i]=s[i-1]+x;
 81             if (!x)
 82             {
 83                 ++c;
 84                 s0+=c*(c+1)/2;
 85             }
 86             else c=0;
 87         }
 88         for (int i=1; i<=s[n]; i++) ans[i]=0;
 89         for (int i=1; i<=n; i++)
 90         {
 91             a[s[i]].x+=i;
 92             b[-s[i-1]+s[n]].x+=1;
 93         }
 94         calc(s[n],1);
 95         for (int i=1; i<=n; i++)
 96         {
 97             a[s[i]].x+=1;
 98             b[-s[i-1]+s[n]].x+=i-1;
 99         }
100         calc(s[n],-1);
101         printf("%lld
",s0);
102         for (int i=1; i<=s[n]; i++)
103             printf("%lld
",(ll)(ans[i]+0.5));
104     }
105 }
View Code

hdu5306

非常涨姿势的题目

由于区间取最小值不能简单的打tag,所以要考虑一些写操作

一种写法是http://www.shuizilong.com/house/archives/hdu-5306-gorgeous-sequence/

维护每个区间内有多少个结点,已被子树中的tag影响

然后每次区间取min就要在处理区间内的子树节点清除比当前修改值大的节点

据说通过均摊分析可以证明是O(nlogn)

还有一种jiry线段树,是维护最大值,严格次大值的做法……

  1 #include<bits/stdc++.h>
  2 
  3 using namespace std;
  4 typedef long long ll;
  5 struct node{int c,mx,tg,l,r; ll s;} tr[1000000*4];
  6 int n,m;
  7 void up(int i)
  8 {
  9     int l=i*2,r=i*2+1;
 10     tr[i].s=tr[l].s+tr[r].s;
 11     tr[i].c=tr[l].c+tr[r].c;
 12     tr[i].mx=max(tr[l].mx,tr[r].mx);
 13 }
 14 
 15 void cov(int i,int len,int w)
 16 {
 17     if (tr[i].tg&&tr[i].tg<=w) return;
 18     tr[i].tg=w;
 19     tr[i].s+=(ll)(len-tr[i].c)*w;
 20     if (tr[i].c<len) tr[i].mx=w;
 21     tr[i].c=len;
 22 }
 23 
 24 void push(int i,int l,int r)
 25 {
 26     if (!tr[i].tg) return;
 27     int m=(l+r)>>1;
 28     cov(i*2,m-l+1,tr[i].tg);
 29     cov(i*2+1,r-m,tr[i].tg);
 30 }
 31 
 32 void build(int i,int l,int r)
 33 {
 34     tr[i].tg=0;
 35     if (l==r)
 36     {
 37         scanf("%d",&tr[i].tg);
 38         tr[i].c=1;
 39         tr[i].mx=tr[i].s=tr[i].tg;
 40         return;
 41     }
 42     int m=(l+r)>>1;
 43     build(i*2,l,m);
 44     build(i*2+1,m+1,r);
 45     up(i);
 46 }
 47 
 48 void dfs(int i,int l,int r,int w)
 49 {
 50     if (tr[i].mx<=w) return;
 51     if (tr[i].tg>=w) tr[i].tg=0; 
 52     if (l==r)
 53     {
 54         tr[i].mx=tr[i].s=tr[i].tg;
 55         tr[i].c=(tr[i].tg!=0);
 56         return;
 57     }
 58     push(i,l,r);
 59     int m=(l+r)>>1;
 60     dfs(i*2,l,m,w);
 61     dfs(i*2+1,m+1,r,w);
 62     up(i);
 63 }
 64 
 65 void work(int i,int l,int r,int x,int y,int w)
 66 {
 67     if (tr[i].mx<=w) return;
 68     if (x<=l&&y>=r)
 69     {
 70         dfs(i,l,r,w);// 清理其他大于w的标记
 71         cov(i,r-l+1,w);
 72         return;
 73     }
 74     int m=(l+r)>>1;
 75     push(i,l,r);
 76     if (x<=m) work(i*2,l,m,x,y,w);
 77     if (y>m) work(i*2+1,m+1,r,x,y,w);
 78     up(i);
 79 }
 80 
 81 int askmx(int i,int l,int r,int x,int y)
 82 {
 83     if (x<=l&&y>=r) return tr[i].mx;
 84     int m=(l+r)>>1,s=0;
 85     push(i,l,r);
 86     if (x<=m) s=max(s,askmx(i*2,l,m,x,y));
 87     if (y>m) s=max(s,askmx(i*2+1,m+1,r,x,y));
 88     return s;
 89 }
 90 
 91 ll asks(int i,int l,int r,int x,int y)
 92 {
 93     if (x<=l&&y>=r) return tr[i].s;
 94     int m=(l+r)>>1; ll s=0;
 95     push(i,l,r);
 96     if (x<=m) s+=asks(i*2,l,m,x,y);
 97     if (y>m) s+=asks(i*2+1,m+1,r,x,y);
 98     return s;
 99 }
100 
101 int main()
102 {
103     int cas;
104     scanf("%d",&cas);
105     while (cas--)
106     {
107         scanf("%d%d",&n,&m);
108         build(1,1,n);
109         for (int i=1; i<=m; i++)
110         {
111             int ch,l,r;
112             scanf("%d%d%d",&ch,&l,&r);
113             if (ch==0)
114             {
115                 int x;
116                 scanf("%d",&x);
117                 work(1,1,n,l,r,x);
118             }
119             else {
120                 if (ch==1) printf("%d
",askmx(1,1,n,l,r));
121                 else printf("%lld
",asks(1,1,n,l,r));
122             }
123         }
124     }
125 }
View Code

hdu5304

求含有n条边的连通图个数

显然这是一个只含单环的图,我们只要穷举所有可能的环及方案数

然后把那个环缩点,这样就等价成了求生成树的个数,这是可以用基尔霍夫矩阵做的

找环可以用状压dp,设f[i][st]表示当前在i,路径走过点的集合为st

对于环,我们都是以编号最小的点为起点,这样每个环只会被统计两次

然后就是缩点求生成树的个数,图G生成树的个数是基尔霍夫矩阵(=度数矩阵[G]-邻接矩阵[G])的任何一个n-1阶主子式的行列式的绝对值。

行列式用高斯消元求即可

  1 #include<bits/stdc++.h>
  2 
  3 using namespace std;
  4 const int mo=998244353;
  5 typedef long long ll;
  6 int c[20][20];
  7 int s[300010],f[20][300010],a[20][20],b[20],n,m;
  8 void inc(int &a,int b)
  9 {
 10     a+=b;
 11     if (a>mo) a-=mo;
 12 }
 13 
 14 ll quick(ll x,int y)
 15 {
 16     ll s=1;
 17     while (y)
 18     {
 19         if (y&1) s=s*x%mo;
 20         x=x*x%mo;
 21         y>>=1;
 22     }
 23     return s;
 24 }
 25 
 26 ll gauss(int n)
 27 {
 28     int f=1; ll ans=1;
 29     for (int i=1; i<=n; i++)
 30     {
 31         if (c[i][i]==0)
 32         {
 33             f=-f;
 34             int j=i+1;
 35             while (j<=n&&c[j][i]==0) j++;
 36             if (j>n) return 0;
 37             for (int k=i; k<=n; k++)
 38                 swap(c[i][k],c[j][k]);
 39         }
 40         ans=ans*c[i][i]%mo;
 41         ll x=quick(c[i][i],mo-2);
 42         for (int j=i+1; j<=n; j++)
 43         {
 44             ll t=x*c[j][i]%mo;
 45             for (int k=i; k<=n; k++)
 46                 c[j][k]=(c[j][k]-t*c[i][k]%mo)%mo;
 47         }
 48     }
 49     ans=(ans+mo)%mo;
 50     if (f==1) return ans;
 51     else return mo-ans;
 52 }
 53 
 54 void get()
 55 {
 56     memset(s,0,sizeof(s));
 57     memset(f,0,sizeof(f));
 58     for (int i=1; i<=n; i++) f[i][1<<(i-1)]=1;
 59     for (int st=1; st<(1<<n); st++)
 60     {
 61         int b;
 62         for (int i=1; i<=n; i++)
 63             if (st&(1<<(i-1))) {b=i;break;}
 64         for (int i=b; i<=n; i++)
 65             if (f[i][st])
 66             {
 67                 for (int j=b+1; j<=n; j++)
 68                     if (a[i][j]&&((st&(1<<(j-1)))==0)) inc(f[j][st|(1<<(j-1))],f[i][st]);
 69                 if (a[i][b]) inc(s[st],f[i][st]);
 70             }
 71     }
 72 }
 73 
 74 int main()
 75 {
 76     while (scanf("%d%d",&n,&m)!=EOF)
 77     {
 78         memset(a,0,sizeof(a));
 79         for (int i=1; i<=m; i++)
 80         {
 81             int x,y;
 82             scanf("%d%d",&x,&y);
 83             a[x][y]=a[y][x]=1;
 84         }
 85         get();
 86         int ans=0,inv=quick(2,mo-2);
 87         for (int st=1; st<(1<<n); st++)
 88         {
 89             if (!s[st]) continue;
 90             memset(b,0,sizeof(b));
 91             memset(c,0,sizeof(c));
 92             int t=1;
 93             for (int j=1; j<=n; j++)
 94                 if (st&(1<<(j-1))) b[j]=1; else b[j]=++t;
 95             if (t>=n-1) continue;
 96             for (int i=1; i<n; i++)
 97                 for (int j=i+1; j<=n; j++)
 98                     if (b[i]!=b[j]&&a[i][j])
 99                     {
100                         c[b[i]][b[i]]++;
101                         c[b[j]][b[j]]++;
102                         c[b[i]][b[j]]--;
103                         c[b[j]][b[i]]--;
104                     }
105             inc(ans,gauss(t-1)*s[st]%mo);
106             //cout <<"*"<<s[st]<<" "<<st<<" "<<endl;
107         }
108         printf("%d
",1ll*ans*inv%mo);
109     }
110 }
View Code

hdu5309

不难想到二分答案,关键是怎么验证和怎么求方案

注意到如果在第i秒嗑药,那么不管前面怎么操作,第i秒后血量

首先求出b[i],表示在i点嗑药后能坚持到哪一秒,b[]具有单调性可以O(n)求出,顺便我们可以判断出两种poor的情形

下面在二分答案y的验证最小嗑药间隔len的时候,令d[]表示从i到n秒可以磕几次药

然后如果d[i]>d[i+1],那么第i秒可以嗑药由此可以转移

最后只要判断d[1]是否大于d[b[0]+1]即可,因为初始最多支撑到第b[0]秒,要保证这段区间里磕了药

输出方案时由于我们已经判断出每一秒是否能嗑药了,显然我们只要从第一个能嗑药的时候往下一次找即可

 1 #include<bits/stdc++.h>
 2 
 3 using namespace std;
 4 typedef long long ll;
 5 const int inf=1e9+7;
 6 int nex[500010],d[500010],b[500010],q[500010],a[500010],n,m,k,t;
 7 bool check(int l)
 8 {
 9     d[n]=0;
10     for (int i=n-1; i; i--)
11         if (b[i]>n) d[i]=d[i+1]+1;
12         else if (b[i]-i<l) d[i]=d[i+1];
13         else d[i]=d[i+1]+(d[i+l]>d[b[i]+1]);
14     return d[1]>d[b[0]+1];
15 }
16 
17 void work()
18 {
19     for (int i=0,j=0; i<n; i++)
20     {
21         while (j<=n&&i*k+a[j]>0) j++;
22         b[i]=(j>n)?inf:j-1;
23         if (b[i]<=i)
24         {
25             puts("Poor Hero!");
26             return;
27         }
28     }
29     if (b[0]>n||b[b[0]]>n)
30     {
31         puts("Poor JRY!");
32         return;
33     }
34     int l=1,r=n,ans=n;
35     while (l<=r)
36     {
37         int mid=(l+r)>>1;
38         if (check(mid))
39         {
40             ans=mid;
41             l=mid+1;
42         }
43         else r=mid-1;
44     }
45     check(ans);
46     printf("%d
",ans);
47     nex[n]=0;
48     for (int i=n-1; i; i--)
49         nex[i]=(d[i]>d[i+1])?i:nex[i+1];
50     int t=0,x=nex[1];
51     while (x)
52     {
53         q[++t]=x;
54         if (b[x]>n) break;
55         if (x+ans<n) x=nex[x+ans]; else break;
56     }
57     printf("%d
",t);
58     for (int i=1; i<=t; i++)
59     {
60         printf("%d",q[i]);
61         if (i!=t) printf(" "); else puts("");
62     }
63 }
64 
65 int main()
66 {
67     while (scanf("%d%d%d",&n,&m,&k)!=EOF)
68     {
69         a[0]=m;
70         for (int i=1; i<=n; i++)
71         {
72             scanf("%d",&a[i]);
73             a[i]=a[i-1]-a[i];
74         }
75         work();
76     }
77 }
View Code
原文地址:https://www.cnblogs.com/phile/p/7090849.html