11月30日考试 题解

T1

原题:CF1416A

显然数$c$为$k$连数当$k$大于等于其所有出现位置中相邻位置距离最大值。然后随便做。时间复杂度$O(n\log n)$,可以优化到$O(n)$。

T2

原题:P5857

计数蒟蒻实锤QAQ。

可以发现,当一行/列的状态被改变时,它一定被异或了奇数次。若有$i$行$j$列被异或了奇数次,那么对答案的贡献是$\binom{n}{i}\times \binom{m}{j}$,其中$i,j$与$k$奇偶性相同(奇×奇=奇,偶×奇=偶)。然后考虑去重。当两种方案所达到最后矩阵的状态相同时,这两种方案是重复的。不难发现只有当$n,m$均为偶数时,才有可能发生这种情况(方案可以对称过去)。对于一组合法的$i,j$,当$n-i,m-j$与$k$奇偶性相同时,这两种方案是重复的。可以分开统计总方案数和重复的方案数,然后减一下即可。

代码:

#include<cstdio>
#include<iostream>
#define int long long
using namespace std;
const int N=200000;
const int mod=998244353;
int fac[N+5],inv[N+5],n,m,k,T;
inline int read()
{
    int x=0,f=1;char ch=getchar();
    while(!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
    while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
inline int qpow(int x,int y)
{
    int res=1;
    while(y)
    {
        if (y&1) res=(res*x)%mod;
        x=(x*x)%mod;
        y>>=1;
    } 
    return res;
}
inline int C(int x,int y){
    return fac[x]*inv[y]%mod*inv[x-y]%mod;
}
signed main()
{
    fac[0]=inv[0]=1;
    for (int i=1;i<=N;i++) fac[i]=(fac[i-1]*i)%mod;
    inv[N]=qpow(fac[N],mod-2);
    for (int i=N-1;i>=1;i--) inv[i]=inv[i+1]*(i+1)%mod;
    T=read();
    while(T--)
    {
        n=read();m=read();k=read();
        int ax=0,ay=0,bx=0,by=0,nn=min(n,k),mm=min(m,k);
        for (int i=nn%2;i<=nn;i+=2) ax=(ax+C(n,i))%mod;
        for (int i=mm%2;i<=mm;i+=2) ay=(ay+C(m,i))%mod;
        if (n%2==0&&m%2==0)
        {
            int l=max(n-k,0ll),r=min(n,k);
            for (int i=l;i<=r;i+=2) bx=(bx+C(n,i))%mod;
            l=max(m-k,0ll),r=min(m,k);
            for (int i=l;i<=r;i+=2) by=(by+C(m,i))%mod;
        }
        printf("%lld\n",(ax*ay%mod-bx*by%mod*inv[2]%mod+mod)%mod);
    }
    return 0;
}

T3

题目大意:给定一棵树,求$\sum\limits_{i=1}^n \sum\limits_{j=i+1}^n (a_i\ xor\ a_j)\times dist(i,j)$。其中每条边边权为$1$。

首先可以对每一位分别统计贡献。发现边权为$1$,不妨考虑对每条边统计贡献,看它被经过多少次。两个数异或对答案有贡献当且仅当这两位不相同。所以统计一下子树内$0$的个数和$1$的个数然后乘起来即可。最后相加即为答案。

T4

题目大意:给定$n$个体积小于等于$k$的物品和一个体积为$m$的背包。当背包容量分别为$1$到$m$时求能装的最大价值。$n\leq 500000,k\leq 5$。

正解不太懂,只会一个正确性不知道怎么证明的乱搞做法……暂且说一下吧。

首先按照性价比从大到小排序,然后维护一个后缀的背包。设$g[i][j]$表示考虑到$i$时剩余量为$j$的最大价值,倒着枚举$i$。这个剩余量比较玄学,设成85左右即可。这样做的意义在于:我们的策略肯定是选一部分前缀,然后在后面选一部分。然后维护一个$ans$统计答案即可。

AlanSP有一个神仙决策单调性做法orz,可以去看看他的博客。

代码:

#include<cstdio>
#include<iostream>
#include<algorithm>
#define all 100
#define int long long
using namespace std;
const int N=500005;
int n,m,k,g[N][105],ans[N],now,val;
struct node{
    int w,v;
}p[N];
inline int read()
{
    int x=0,f=1;char ch=getchar();
    while(!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
    while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
bool cmp(node x,node y){
    return x.v*y.w>y.v*x.w;
}
signed main()
{
    n=read();m=read();k=read();
    for (int i=1;i<=n;i++) p[i].w=read(),p[i].v=read();
    sort(p+1,p+n+1,cmp);
    for (int i=n;i>=1;i--)
    {
        for (int j=all;j>=p[i].w;j--) g[i][j]=g[i+1][j-p[i].w]+p[i].v;
        for (int j=0;j<=all;j++) g[i][j]=max(g[i][j],g[i+1][j]);
    }
    for (int i=1;i<=n;i++)
    {
        for (int j=0;j<=all;j++) ans[now+j]=max(ans[now+j],val+g[i][j]);
        now+=p[i].w;val+=p[i].v;
    }
    for (int i=1;i<=m;i++) printf("%lld\n",ans[i]);
    return 0;
}

 

posted @ 2020-11-30 16:23  我亦如此向往  阅读(123)  评论(0编辑  收藏  举报