9.7题解

T1

考场上什么都没想到,事实上我们会发现,后面所有的位置做的贡献实质上都是最初的第一列和第一行作出的贡献,那么我们完全可以去计算这一列和一行给最终的答案作出了多少贡献

显然对于第一列的所有点,他做的贡献是从他到$(n,m)$横着经过的$m$个点每次乘$a$,竖着经过的$n-i$个点每次乘$b$,这样的话就是一个快速幂的问题,但是对于不同的路径他都会作出贡献,所以还需要乘上一个方案数,注意,由于他不会给第一列的其他位置做贡献,所以第一步是确定的,就是往右走,算组合数的时候需要注意一下,那么同理对于第一行的数也一样,这种计算这一位对答案的贡献的思路实际上很常用,但是我当时满脑子都是矩阵快速幂,一直在构造系数矩阵,所以我死了

 1 #include<iostream>
 2 #include<cstdio>
 3 #define mod 998244353
 4 #define maxn 300300
 5 #define ll long long
 6 using namespace std;
 7 int n,m;
 8 ll a,b,ans,ami,bmi;
 9 ll h[maxn],z[maxn];
10 ll jc[maxn*2];
11 ll ksm(ll a,int b)
12 {
13     ll ans=1;
14     while(b)
15     {
16         if(b&1)  ans=(ans*a)%mod;
17         b=b>>1;  a=(a*a)%mod;
18     }
19     return ans;
20 }
21 ll C(int a,int b)
22 {
23     ll ny1=ksm(jc[a-b],mod-2),ny2=ksm(jc[b],mod-2);
24     ll ny=(ny1*ny2)%mod;
25     return  (jc[a]*ny)%mod;
26 }
27 int main()
28 {
29 //    freopen("a_sample2.in","r",stdin);
30     scanf("%d%d%lld%lld",&n,&m,&a,&b);  a%=mod;  b%=mod;
31     ami=ksm(a,m);  bmi=ksm(b,n);  jc[0]=1;
32     for(int i=1;i<=n+m;++i)  jc[i]=(jc[i-1]*i)%mod;
33     for(int i=1;i<=n;++i)  {scanf("%lld",&h[i]);  h[i]%=mod;}
34     for(int i=1;i<=m;++i)  {scanf("%lld",&z[i]);  z[i]%=mod;}
35     for(int i=1;i<=n;++i)//m步走完,坐标为(i,0)
36     {
37         ll mi=ksm(b,n-i),c=C(n-i+m-1,m-1);
38         ans=(ans+(((ami*mi)%mod*c)%mod*h[i])%mod)%mod;
39     }
40     for(int i=1;i<=m;++i)//n步走完,坐标为(0,i)
41     {
42         ll mi=ksm(a,m-i),c=C(n+m-i-1,n-1);
43         ans=(ans+(((mi*bmi)%mod*c)%mod*z[i])%mod)%mod;
44     }
45     printf("%lld\n",ans);
46     return 0;
47 }
View Code

T2

这道题有一个很巧妙的转化,他把$y$方点当作边,$x$方点当作点,这样建图的话就是$n$个点,$n$条边,那显然是一个基环树,题解选择了对于基环树少建一条环上的边,这样的话就变成了一棵树,而少建的那条边对应一个$y$,那么在断开的两个点里,最少选一个点就可以保证$y$被激活

那么题意就变为给你一棵树,对于每条边的两个端点,至少选一个点才能保证这条边被覆盖,要求覆盖所有的边,且花费最小,这就变成了一个树上dp

设$f[i]$代表选上i这个点,使得他的子树里所有的边都被覆盖的最少花费,$g[i]$代表不选这个点,仍保证他的子树里所有的点被覆盖的最小花费,考虑转移

如果这个点不选,那他所有的子节点都必须选才能覆盖他和他儿子之间的边,如果这个点选,那他儿子选或不选就不重要了,直接取$min$加给他就可以了

我们分别以断开的边的两个端点为跟进行一遍dp,由于前面提到的我们搞掉了一条边,但是这条边也需要被覆盖,所以两个断点里我们必须选一个,也就是在$f[u]$和$f[v]$里取个$max$就好了

关于选择环里的一条边断开,我是用并查集维护的

 1 //如果当前点选,那么他的子节点选或不选无所谓,取个min就可以了
 2 //如果当前点不选,那他的子节点必须都选
 3 #include<iostream>
 4 #include<cstdio>
 5 #include<vector>
 6 #define maxn 1001000
 7 using namespace std;
 8 int n,a,b,xh,xb,js,ans,u,v;
 9 int head[maxn],cos[maxn],f[maxn],g[maxn],fa[maxn],to[maxn*2],xia[maxn*2];
10 inline int read()
11 {
12     int e=0;  char ch=getchar();
13     while(ch<'0'||ch>'9')  ch=getchar();
14     while(ch>='0'&&ch<='9')  {e=(e<<3)+(e<<1)+(ch^48);  ch=getchar();}
15     return e;
16 }
17 inline void add(int x,int y)
18 {
19     to[++js]=y;  xia[js]=head[x];  head[x]=js;
20 }
21 void dfs(int x,int faa)
22 {
23     f[x]=cos[x];  g[x]=0;
24     for(int i=head[x];i;i=xia[i])
25     {
26         int ls=to[i];
27         if(ls==faa)  continue;
28         dfs(ls,x);  g[x]+=f[ls];  f[x]+=min(g[ls],f[ls]);
29     }
30 }
31 int find(int x)
32 {
33     if(fa[x]!=x)  fa[x]=find(fa[x]);
34     return fa[x];
35 }
36 void hb(int x,int y)
37 {
38     x=find(x);  y=find(y);  fa[x]=y;
39 }
40 int main()
41 {
42 //    freopen("b_sample2.in","r",stdin);
43     n=read();  a=read();  b=read();
44     for(int i=1;i<=n;++i)  fa[i]=i;
45     for(int i=1;i<=n;++i)
46     {
47         xh=read();  xb=read();  cos[xh]+=a;  cos[xb]+=b;
48         if(find(xh)==find(xb))  {u=xh;  v=xb;  continue;}
49         add(xh,xb);  add(xb,xh);  hb(xh,xb);
50     }
51     dfs(u,-1);  ans=f[u];  dfs(v,-1);  ans=min(ans,f[v]);
52     printf("%d\n",ans);
53     return 0;
54 }
View Code

T3

因为最后的答案是$(-1)^x$的和,所以我们并不关心$\sumd$的准确的值,只关心他的奇偶性,对于$i{\times}j$里所有的数$x$,如果$d(x)$为偶数,那么他对做后的$\sum$的奇偶并不会产生影响,我们只关心$d(x)$为奇数的$x$,题解说不难想到此时$x$为完全平方数,我觉得很难想到,又因为$x$是$i$和$j$的乘积,所以如果我们把$i$变成$p*q^2$的形式,那么$j$就必须可以写成$p*r^2$的形式,那对于每一个$i$,就有$\sqrt{\frac{m}{p}}$个$j$产生贡献,所以关键就是每个$i$对应的$p$的求法,一种方法就是通过线筛的形式进行求解,我觉得很迷并且不会证明,但是我就是这么打的,还有一种方法就是枚举$p$和$q$去找对应的$i$,我觉得这个更好理解一点

 1 //1-m内有根号(m/p)个数与i的乘积的约数和为奇数
 2 //p是i筛掉所有完全平方数之后剩下的数
 3 #include<iostream>
 4 #include<cstdio>
 5 #include<cmath>
 6 #define ll long long
 7 #define maxn 10001000
 8 using namespace std;
 9 int n,ans,cnt;
10 ll m;
11 int p[maxn],pd[maxn],zs[maxn];
12 int main()
13 {
14     scanf("%d%lld",&n,&m);  p[1]=1;
15     for(int i=2;i<=n;++i)
16     {
17         if(pd[i]==0)  {pd[i]=1;  p[i]=i;  zs[++cnt]=i;}
18         for(int j=1;j<=cnt&&i*zs[j]<=n;j++)
19         {
20             pd[i*zs[j]]=1;
21             if(p[i]%zs[j]==0)  p[i*zs[j]]=p[i]/zs[j];
22             else  p[i*zs[j]]=p[i]*p[zs[j]];
23             if(!(i%zs[j]))  break;
24         }
25     }
26     for(int i=1;i<=n;++i)
27     {
28         ll ls=m/(1ll*p[i]);  ls=sqrt(ls);
29         if(ls%2==0)  ans++;
30         else  ans--;
31     }
32     printf("%d\n",ans);
33     return 0;
34 }
线筛
 1 #include<iostream>
 2 #include<cstdio>
 3 #include<bitset>
 4 #include<cmath>
 5 using namespace std;
 6 #define ll long long
 7 #define maxn 10000010
 8 ll n,m,js,ans,o,ed;
 9 bitset<maxn> pd;
10 int pri[maxn],v[maxn],num;
11 void prime(int x)
12 {
13     for(int i=2;i<=x;++i)
14     {
15         if(!v[i]){v[i]=i; pri[++num]=i;}
16         for(int j=1;j<=num;++j)
17         {
18             if(v[i]<pri[j]||pri[j]*i>n) break;
19             v[i*pri[j]]=pri[j];
20         }
21     }
22 }
23 void shai(int x)
24 {
25     for(int i=1;i<=num;++i)
26     {
27         o=pri[i]*pri[i];
28         for(int j=o;j<=x;j+=o) pd[j]=1;
29     }
30 }
31 int main()
32 {    
33     //freopen("c2.in","r",stdin);
34     scanf("%lld%lld",&n,&m);
35     prime(sqrt(n)); shai(n);
36     ed=sqrt(n); //pd[1]=1;
37     for(int q=1;q<=ed;++q)
38         for(int p=1;p*q*q<=n;++p)
39         {
40             o=sqrt(m/p);
41             if((!pd[p])&&(o%2)) ans--;
42         }
43     //cout<<ans;
44     ans*=2;
45     ans+=n;
46     printf("%lld",ans);
47     return 0;
48 }
枚举,来自itawbm小姐姐的代码

猜你喜欢

转载自www.cnblogs.com/hzjuruo/p/11596421.html
9.7