Educational Codeforces Round 86 (Div. 2) / contest 1342


题目地址:https://codeforces.com/contest/1342



A Road To Zero

题意

思路

代码

#include <bits/stdc++.h>
#define mem(a,b) memset(a,b,sizeof(a))
#define REP(i,a,b) for(int i=(a);i<=(int)(b);i++)
#define REP_(i,a,b) for(int i=(a);i>=(b);i--)
using namespace std;
typedef long long LL;
typedef vector<int> VI;
int read()
{
    int x=0,flag=1;
    char c=getchar();
    while((c>'9' || c<'0') && c!='-') c=getchar();
    if(c=='-') flag=0,c=getchar();
    while(c<='9' && c>='0') {x=(x<<3)+(x<<1)+c-'0';c=getchar();}
    return flag?x:-x;
}

int main()
{
    //freopen("input.txt","r",stdin);
    int T=read();
    while(T--)
    {
        LL x=read(),y=read(),a=read(),b=read();
        if(x>y) swap(x,y);
        printf("%lld\n",b<2*a?x*b+(y-x)*a:(x+y)*a);
    }

    return 0;
}



B Binary Period

题意

思路

代码

#include <bits/stdc++.h>
#define mem(a,b) memset(a,b,sizeof(a))
#define REP(i,a,b) for(int i=(a);i<=(int)(b);i++)
#define REP_(i,a,b) for(int i=(a);i>=(b);i--)
using namespace std;
typedef long long LL;
typedef vector<int> VI;
int read()
{
    int x=0,flag=1;
    char c=getchar();
    while((c>'9' || c<'0') && c!='-') c=getchar();
    if(c=='-') flag=0,c=getchar();
    while(c<='9' && c>='0') {x=(x<<3)+(x<<1)+c-'0';c=getchar();}
    return flag?x:-x;
}

int main()
{
    //freopen("input.txt","r",stdin);
    char t[105];
    int T=read();
    while(T--)
    {
        int n0=0,n1=0;
        scanf("%s",t+1);
        for(int i=1;t[i];i++) n0+=t[i]=='0',n1+=t[i]=='1';
        if(!(n0 && n1)) puts(t+1);
        else
        {
            putchar(t[1]);
            for(int i=2;t[i];i++)
            {
                if(t[i]==t[i-1]) putchar(t[i]=='0'?'1':'0');
                putchar(t[i]);
            }
            puts("");
        }
    }

    return 0;
}



C Yet Another Counting Problem

题意:给出 a 和 b,然后有多组询问,每组询问给出一个 l 和 r,要求出 [l, r] 范围内满足 ((x mod a) mod b) ≠ ((x mod b) mod a) 的 x 的个数。

思路:显然我们要算的是 [0, r] 范围内 ((x mod a) mod b) = ((x mod b) mod a) 的个数,然后就可以算出答案。通过打表可以找出规律:假如 a<b,c=lcm(a, b)(最小公倍数),那么凡是区间 [kc, kc+b-1] 内的都是满足相等这个条件的。

代码

#include <bits/stdc++.h>
#define mem(a,b) memset(a,b,sizeof(a))
#define REP(i,a,b) for(int i=(a);i<=(int)(b);i++)
#define REP_(i,a,b) for(int i=(a);i>=(b);i--)
using namespace std;
typedef long long LL;
typedef vector<int> VI;
int read()
{
    int x=0,flag=1;
    char c=getchar();
    while((c>'9' || c<'0') && c!='-') c=getchar();
    if(c=='-') flag=0,c=getchar();
    while(c<='9' && c>='0') {x=(x<<3)+(x<<1)+c-'0';c=getchar();}
    return flag?x:-x;
}

LL cal(LL x,LL y,LL r)
{
    LL t=r/x,ret=y*t;
    r-=x*t;
    if(r<=y-1) ret+=r+1;
    else ret+=y;
    return ret;
}

int main()
{
    //freopen("input.txt","r",stdin);
    int T=read();
    while(T--)
    {
        LL a=read(),b=read();
        int q=read();
        LL x=a*b/__gcd(a,b),y=max(a,b);
        while(q--)
        {
            LL l,r;
            scanf("%lld%lld",&l,&r);
            LL ans=cal(x,y,r)-cal(x,y,l-1);
            printf("%lld ",r-l+1-ans);
        }
        puts("");
    }

    return 0;
}



D Multiple Testcases

题意:每个数组有一个长度,要把 n 个数组分成若干堆,要求每一堆中长度大于等于 k 的数组个数小于等于 c k c_k ck ,c 是一个给出的单调不增数组。问最少可以分成多少堆。

思路:很明显的二分思路。对于某一个堆数 x,考虑最坏的那个堆的情况,一个堆越坏,说明它里面长度大的数组更多,那么最好的分配方式显然就是把所有数组长度从大到小排序,然后轮流循环分配,这样可以保证最坏的堆最好。所以最坏的堆就是分配了 1, x+1, 2x+1, … 这些下标对应的长度的那个堆,我们看它是否满足 c 数组的要求就可以了。

代码

#include <bits/stdc++.h>
#define mem(a,b) memset(a,b,sizeof(a))
#define REP(i,a,b) for(int i=(a);i<=(int)(b);i++)
#define REP_(i,a,b) for(int i=(a);i>=(b);i--)
using namespace std;
typedef long long LL;
typedef vector<int> VI;
int read()
{
    int x=0,flag=1;
    char c=getchar();
    while((c>'9' || c<'0') && c!='-') c=getchar();
    if(c=='-') flag=0,c=getchar();
    while(c<='9' && c>='0') {x=(x<<3)+(x<<1)+c-'0';c=getchar();}
    return flag?x:-x;
}

const int maxn=2e5+5;
int n,k,m[maxn],c[maxn];

bool can(int x)
{
    for(int i=1,j=1;i<=n;i+=x,j++)
    {
        if(c[m[i]]<j) return 0;
    }
    return 1;
}

int main()
{
    //freopen("input.txt","r",stdin);
    n=read(),k=read();
    REP(i,1,n) m[i]=read();
    REP(i,1,k) c[i]=read();
    sort(m+1,m+n+1,greater<int>());
    int l=0,r=n,mid;
    while(l<r-1)
    {
        mid=(l+r)>>1;
        if(can(mid)) r=mid;
        else l=mid;
    }
    printf("%d\n",r);
    for(int i=1;i<=r;i++)
    {
        VI x;
        for(int j=i;j<=n;j+=r)
            x.push_back(m[j]);
        printf("%d ",x.size());
        for(int j:x) printf("%d ",j);
        puts("");
    }

    return 0;
}



E Placing Rooks

题意:在一个 n×n 的棋盘上放置 n( n ≤ 1 0 6 n\le 10^6 n106) 个车,使得满足以下两个条件:(1)所有空格子都可以被至少一辆车攻击;(2)恰好有 k 对车可以相互攻击(这个只有同行或同列并且相邻的才可以)。问有多少种放置方案?

思路:我们考虑每一行放一辆车,这样可以满足条件(1),然后最后的答案乘 2 就行了(也可以每一列放一辆车)。如果某一列放了 x 辆车(x>0),那么这一列可以相互攻击的车的对数为 x-1,根据这样分析,我们应当恰好在 n-k 个列中放置,其它列不放车,所以设 m=n-k,这里就有 C n m C_n^{m} Cnm 中选择;现在已经规定好哪些列可以放了,并且这些列至少要放一个,这其实就是一个第二类斯特林数的问题(相当于把 n 个不同的行号放进 m 个列之中),可以用容斥定理解决,这里由于列是有编号的,也就是有序的,所以在第二类斯特林数的基础上还应该乘 m! ,也就是方案数为 ∑ i = 0 m ( − 1 ) i C m i ( m − i ) n \sum\limits_{i=0}^{m}(-1)^iC_m^i(m-i)^n i=0m(1)iCmi(mi)n ;结合之前的分析可以得出总方案数为 2 × C n m × ( ∑ i = 0 m ( − 1 ) i C m i ( m − i ) n ) 2\times C_n^m \times (\sum\limits_{i=0}^{m}(-1)^iC_m^i(m-i)^n) 2×Cnm×(i=0m(1)iCmi(mi)n)

做题的时候我被旁边的 tag 误导了,求出了 ∑ i = 0 m ( − 1 ) i C m i ( m − i ) n \sum\limits_{i=0}^{m}(-1)^iC_m^i(m-i)^n i=0m(1)iCmi(mi)n 以为要用 fft,用 fft 可以算出每一个 m 对应的答案,但是由于这里数比较大,精度不一定可以,我还担心了半天,后来发现这里只用求一个 m,暴力算出来就行了。

还有个细节就是当 k=0 的时候相当于恰好每一行每一列都是一辆车,所以不用乘 2 。

代码

#include <bits/stdc++.h>
#define mem(a,b) memset(a,b,sizeof(a))
#define REP(i,a,b) for(int i=(a);i<=(int)(b);i++)
#define REP_(i,a,b) for(int i=(a);i>=(b);i--)
using namespace std;
typedef long long LL;
typedef vector<int> VI;
int read()
{
    int x=0,flag=1;
    char c=getchar();
    while((c>'9' || c<'0') && c!='-') c=getchar();
    if(c=='-') flag=0,c=getchar();
    while(c<='9' && c>='0') {x=(x<<3)+(x<<1)+c-'0';c=getchar();}
    return flag?x:-x;
}

const int maxn=1e6+5,M=998244353;
int n,m,jie[maxn],jie_[maxn];
LL k;
int ksm(int x,int k)
{
    if(x<=0) return 0;
    int ret=1;
    while(k)
    {
        if(k&1) ret=1ll*ret*x%M;
        x=1ll*x*x%M;
        k>>=1;
    }
    return ret;
}

int C(int n,int m)
{
    if(m>=n || !m) return 1;
    return 1ll*jie[n]*jie_[m]%M*jie_[n-m]%M;
}

int main()
{
    //freopen("input.txt","r",stdin);
    jie[0]=jie_[0]=1;
    REP(i,1,200000)
    {
        jie[i]=1ll*jie[i-1]*i%M;
        jie_[i]=ksm(jie[i],M-2);
    }
    cin>>n>>k;
    if(k>=n) return puts("0"),0;
    m=n-k;
    LL ans=0;
    REP(i,0,m)
    {
        int x=1ll*C(m,i)*ksm(m-i,n)%M;
        if(i&1) x=-x;
        ans+=x;
    }
    ans%=M;
    ans=ans*C(n,m)%M;
    if(k) ans=ans*2%M;
    cout<<(ans%M+M)%M;

    return 0;
}



F Make It Ascending

题意:有一个长度为 n ( 1 ≤ n ≤ 15 1\le n \le 15 1n15)的正整数数组 a,每一次操作可以选择任意的 i 和 j,然后令 a j a_j aj += a i a_i ai,然后把 a i a_i ai 从原来的数组中删掉,同时每个数的下标也会相应变化。问最少操作多少次,可以让数组 a 严格单调递增,并且给出操作序列。

思路:看这个数据范围就知道大概是一个状压dp。设 f ( i , m a s k , p ) f(i,mask,p) f(i,mask,p) 表示 处理了 i 组数据,使用的数据为 mask,最后一组数据在位置 p 时,最后那个数的最小值。然后就可以开始dp。一开始我的写法是对于每个 f ( i , m a s k , p ) f(i,mask,p) f(i,mask,p) ,枚举 mask 的子集 mask2 和最后的位置 p2,然后转移,但是这样的复杂度太高(大概计算是 n 2 4 n n^24^n n24n 级别的,有子集和子集的子集,我不知道该怎么计算T_T),反正跑得太慢了。后来(看题解)发现,好像对于每个可行的 f ( i , m a s k , p ) f(i,mask,p) f(i,mask,p) 往后推会比较好一些:枚举 ( 2 n − 1 ) ⨁ m a s k (2^n-1)\bigoplus mask (2n1)mask 的子集,那么这些子集或mask(这是一个位运算) 就是可以转移过去的子集,然后转移过去的最后的位置 p2 是多少呢,好像 p 后面的为 1 的位置都可以,但是我们贪心地考虑,选择最前面那个位置更好,因为后面的位置可以由更后面的 p 去考虑。

然后输出方案也是真难写……

代码

#include <bits/stdc++.h>
#define mem(a,b) memset(a,b,sizeof(a))
#define REP(i,a,b) for(int i=(a);i<=(int)(b);i++)
#define REP_(i,a,b) for(int i=(a);i>=(b);i--)
#define pb push_back
using namespace std;
typedef long long LL;
typedef vector<int> VI;
int read()
{
    int x=0,flag=1;
    char c=getchar();
    while((c>'9' || c<'0') && c!='-') c=getchar();
    if(c=='-') flag=0,c=getchar();
    while(c<='9' && c>='0') {x=(x<<3)+(x<<1)+c-'0';c=getchar();}
    return flag?x:-x;
}

int n,a[18],inf=1e8;
int f[16][1<<15][16],la[16][1<<15][16][2],sum[1<<15];

int get(int m)
{
    int ret=0;
    for(int i=0;i<n;i++)
        if((1<<i)&m)
            ret+=a[i+1];
    return ret;
}

int main()
{
    //freopen("input.txt","r",stdin);
    int T=read();
    while(T--)
    {
        n=read();
        REP(i,1,n) a[i]=read();
        REP(i,0,n) REP(j,0,(1<<n)-1) REP(k,0,n)
            f[i][j][k]=inf,la[i][j][k][0]=la[i][j][k][1]=0;
        REP(j,0,(1<<n)-1) sum[j]=get(j);

        f[0][0][0]=0;
        for(int i=0;i<n;i++)
            for(int mask=0;mask<(1<<n);mask++)
                for(int p=i;p<n;p++)
                {
                    if(f[i][mask][p]==inf) continue;
                    int mm=((1<<n)-1)^mask;
                    for(int m=mm;m;m=(m-1)&mm)
                    {

                        int mask2=m|mask;
                        if((m>>p)==0) continue;
                        int p2=p+__builtin_ctz(m>>p)+1;
                        if(mask&(1<<(p2-1))) continue;
                        if(sum[m]>f[i][mask][p] && sum[m]<f[i+1][mask2][p2])
                        {
                            f[i+1][mask2][p2]=sum[m];
                            la[i+1][mask2][p2][0]=mask;
                            la[i+1][mask2][p2][1]=p;
                        }
                    }
                }

        int len=0,mm=(1<<n)-1,pp=0;
        REP(i,1,n) REP(p,1,n) if(f[i][(1<<n)-1][p]<inf) len=i,pp=p;

        VI x[n+1],y;
        REP_(l,len,1)
        {
            y.pb(pp);
            int q=mm^la[l][mm][pp][0];
            for(int i=0;i<n;i++) if(((1<<i)&q) && i+1!=pp) x[pp].pb(i+1);
            int p=la[l][mm][pp][1],m=la[l][mm][pp][0];
            pp=p; mm=m;
        }
        vector<pair<int,int> > ans;
        for(int q=y.size()-1,p=y[q];q>=0;p=y[(--q)>=0?q:0])
            for(int i:x[p])
            {
                int ii=i,pp=p;
                REP(j,1,i-1) if(!a[j]) ii--;
                REP(j,1,p-1) if(!a[j]) pp--;
                ans.push_back(make_pair(ii,pp));
                a[p]+=a[i]; a[i]=0;
            }
        printf("%d\n",ans.size());
        for(pair<int,int> p:ans) printf("%d %d\n",p.first,p.second);
    }


    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值