Codeforces Round #596 Div1 A~E题解

我好菜啊

A

题意:
定义p-二进制数为2^k-p,给出n和p,求用最小个数的p-二进制数来表示n
1<=n<=10^9,-1000<=p<=1000

题解:
猜结论,答案不会很大
n可以表示成kp+s的形式,枚举k,判断(n-kp)是否能用k个2的幂构成
画一下图可以发现,如果可以构成,那么满足(n-kp)的位数<=k<=n-kp
(相当于把一颗二叉树上一个点变成两个)
证明答案不会很大:
首先(n-kp)的位数最多为30
①p>=0
显然当k超过30后,如果不满足则之后也不满足
②p<0
那么n-kp显然大于等于k

code

#include <algorithm>
#include <iostream>
#include <cstdlib>
#include <cstring>
#include <cstdio>
#define fo(a,b,c) for (a=b; a<=c; a++)
#define fd(a,b,c) for (a=b; a>=c; a--)
using namespace std;

long long n,s;
int p,i,j,k,l;

bool pd(long long t)
{
    long long T=t;
    int sum=0;

    while (T)
    {
        sum+=T&1;
        T>>=1;
    }

    if (s>=sum && s<=t)
    return 1;
    else
    return 0;
}

int main()
{
//  freopen("a.in","r",stdin);

    scanf("%I64d%d",&n,&p);

    while (!pd(n))
    {
        n-=p;
        ++s;

        if (n<0 ||s>100)
        break;
    }

    if (n>=0 && pd(n))
    printf("%I64d\n",s);
    else
    printf("-1\n");
}

B

题意:
给出n个数和k,求(i,j)的个数(i<j),使得ai*aj=x^k
n<=10^5,2<=k<=100

题解:
能表示成x的k次方,意味着乘积质因数分解后每一位的指数%k=0
也就是(ai中p的指数+aj中p的指数)%k=0,把i或j的指数取负,变成求ai和aj质因数指数相等的(i,j)个数
可以排序搞,根据k讨论一下大质数

code

#include <algorithm>
#include <iostream>
#include <cstdlib>
#include <cstring>
#include <cstdio>
#include <cmath>
#define fo(a,b,c) for (a=b; a<=c; a++)
#define fd(a,b,c) for (a=b; a>=c; a--)
#define min(a,b) (a<b?a:b)
using namespace std;

int a[200001];
int p[200001][66];
int P[317];
int c[200001];
int N,n,K,i,j,k,l,s,len,ls,s1,s2;
long long ans;
bool bz;

bool cmp(int a,int b)
{
    int i;

    fo(i,1,65)
    if (p[a][i]<p[b][i])
    return 1;
    else
    if (p[a][i]>p[b][i])
    return 0;

    return 0;
}

bool pd(int t)
{
    int i;

    fo(i,1,65)
    if (p[a[t]][i]!=p[a[t+1]][i])
    return 0;
    return 1;
}

bool Cmp(int x,int y)
{
    return p[x][0]<p[y][0];
}

int main()
{
//  freopen("b.in","r",stdin);

    fo(i,2,316)
    {
        k=1;

        fo(j,2,floor(sqrt(i)))
        if (!(i%j))
        {
            k=0;
            break;
        }

        if (k)
        P[++len]=i;
    }

    scanf("%d%d",&n,&K);
    fo(i,1,n)
    {
        scanf("%d",&l);
        fo(j,1,len)
        if (!(l%P[j]))
        {
            while (!(l%P[j]))
            {
                l/=P[j];
                ++p[i][j];
            }
            p[i][j]%=K;
            p[i+n][j]=(K-p[i][j])%K;
        }

        if (l>1)
        {
            p[i][0]=l;
            p[i+n][0]=l;
        }
    }

    N=n+n;
    fo(i,1,N)
    a[i]=i;

//  fo(i,1,N)
//  {
//      fo(j,0,5)
//      cout<<p[i][j]<<" ";
//      cout<<endl;
//  }

    sort(a+1,a+N+1,cmp);

//  fo(i,1,N)
//  cout<<a[i]<<endl;
//  fo(i,1,N)
//  {
//      fo(j,0,5)
//      cout<<p[a[i]][j]<<" ";
//      cout<<endl;
//  }

    ls=1;
    fo(i,1,N)
    if (i==N || !pd(i))
    {
        l=0;
        fo(j,ls,i)
        c[++l]=a[j];

        sort(c+1,c+l+1,Cmp);

        if (K>=3)
        {
            s1=0;s2=0;

            fo(j,1,l)
            if (!p[c[j]][0])
            {
                if (c[j]<=n)
                ++s1; else ++s2;
            }

//          fo(j,1,l)
//          if (c[j]<=n)
//          cout<<c[j]<<" ";
//          else
//          cout<<-(c[j]-n)<<" ";
//          cout<<endl;
//          cout<<" "<<s1*s2<<" "<<s1<<" "<<s2<<endl;

            ans+=(long long)s1*s2;
        }
        else
        {
            s1=0;s2=0;
            fo(j,1,l)
            {
                if (j==1 || p[c[j]][0]==p[c[j-1]][0])
                {
                    if (c[j]<=n)
                    ++s1; else ++s2;
                }
                else
                {
                    ans+=(long long)s1*s2;

                    s1=0;s2=0;
                    if (c[j]<=n)
                    ++s1; else ++s2;
                }
            }
            ans+=(long long)s1*s2;
        }

        ls=i+1;
    }

    if (K==2)
    {
        fo(i,1,n)
        {
            fo(j,1,65)
            if ((p[i][j]*2%K))
            break;

            if (j>65)
            --ans;
        }
    }
    else
    {
        fo(i,1,n)
        if (!p[i][0])
        {
            fo(j,1,65)
            if ((p[i][j]*2%K))
            break;

            if (j>65)
            --ans;
        }
    }

    printf("%I64d\n",ans/2);
}

C

题意:
给出n*m的方格图,每个格子上有514石头或者为空
从(1,1)开始向(n,m)移动(只能向右/下),每次移动会把一行/一列的石头向右/下推一格,不能把石头推出方格图外
求方案数

题解:
差点就切了的sb题
一个合法的路径有若干次转折,可以发现每次转折时所转到的方向上的石头都没被推过
所以只需要考虑当前方向往后的石头个数即可转移,用前缀和优化

code

#include <algorithm>
#include <iostream>
#include <cstdlib>
#include <cstring>
#include <cstdio>
#define fo(a,b,c) for (a=b; a<=c; a++)
#define fd(a,b,c) for (a=b; a>=c; a--)
#define mod 1000000007
using namespace std;

bool a[2002][2002];
int s1[2002][2002];
int s2[2002][2002];
int f[2002][2002][2];
int g[2002][2002][2];
int n,m,i,j,k,l;
char ch;

int main()
{
//  freopen("c.in","r",stdin);

    scanf("%d%d",&n,&m);
    fo(i,1,n)
    {
        fo(j,1,m)
        {
            ch=getchar();
            while (ch!='.' && ch!='R')
            ch=getchar();

            a[i][j]=ch=='R';
        }
    }

    if (a[n][m])
    {
        printf("0\n");
        return 0;
    }
    if (n==1 && m==1)
    {
        printf("1\n");
        return 0;
    }

    fd(i,n,1)
    {
        fd(j,m,1)
        {
            s1[i][j]=s1[i][j+1]+a[i][j];
            s2[i][j]=s2[i+1][j]+a[i][j];
        }
    }

    f[1][1][0]=1;
    f[1][1][1]=1;
    fo(i,1,n)
    {
        fo(j,1,m)
        {
            if (i>1 || j>1)
            {
                f[i][j][0]=g[i][j][0];
                f[i][j][1]=g[i][j][1];
            }

            fo(k,0,1)
            if (f[i][j][k])
            {
                if (!k)
                {
                    g[i][j][k^1]=(g[i][j][k^1]+f[i][j][k])%mod;
                    g[i][m-s1[i][j+1]+1][k^1]=(g[i][m-s1[i][j+1]+1][k^1]-f[i][j][k])%mod;
                }
                else
                {
                    g[i][j][k^1]=(g[i][j][k^1]+f[i][j][k])%mod;
                    g[n-s2[i+1][j]+1][j][k^1]=(g[n-s2[i+1][j]+1][j][k^1]-f[i][j][k])%mod;
                }
            }

            g[i][j+1][1]=(g[i][j+1][1]+g[i][j][1])%mod;
            g[i+1][j][0]=(g[i+1][j][0]+g[i][j][0])%mod;
        }
    }

    printf("%d\n",((f[n][m][0]+f[n][m][1])%mod+mod)%mod);
}

D

题意:
把一条链进行若干次操作,每次操作选择一个点,把这个点的父亲设为其父亲的父亲
求最小的操作使得能把链变成给出的一棵树,并给出链的初始编号和每次操作的点编号

题解:
很妙的构造题
一开始想把每个点接到兄弟节点中深度最小的点,但是挂了
考虑把树变成链,一次操作实质是把一个点的父亲设为其的一个兄弟节点
由于每次操作树的深度最多+1,所以操作次数的下限为(n-深度)
先找出树上最长链,每次把最长链上的一个点v下移到兄弟u,下移后最长链多了u
每次深度+1,所以这样的操作次数刚好是(n-深度),即为最优

code

#include <algorithm>
#include <iostream>
#include <cstdlib>
#include <cstring>
#include <cstdio>
#define fo(a,b,c) for (a=b; a<=c; a++)
#define fd(a,b,c) for (a=b; a>=c; a--)
#define min(a,b) (a<b?a:b)
#define max(a,b) (a>b?a:b)
using namespace std;

int a[100001][2];
int ls[100001];
int fa[100001];
int nx[100001];
int Ans[100001];
bool bz[100001];
int d[100001];
int n,i,j,k,l,len,ans,tot,tot2,mx,mx2,h,t;

void New(int x,int y)
{
    ++len;
    a[len][0]=y;
    a[len][1]=ls[x];
    ls[x]=len;
}

void dfs(int t,int d)
{
    int i;

    if (d>mx)
    mx=d,mx2=t;

    for (i=ls[t]; i; i=a[i][1])
    fa[a[i][0]]=t,dfs(a[i][0],d+1);
}

int main()
{
//  freopen("d.in","r",stdin);

    scanf("%d",&n);
    fo(i,2,n)
    {
        scanf("%d",&fa[i]);++fa[i];
        New(fa[i],i);
    }

    dfs(1,1);

    while (mx2)
    {
        bz[mx2]=1;

        nx[fa[mx2]]=mx2;
        mx2=fa[mx2];
    }
    fo(j,1,n)
    if (bz[j])
    {
        for (i=ls[j]; i; i=a[i][1])
        if (!bz[a[i][0]])
        d[++t]=a[i][0];
    }

    while (h<t)
    {
        ++h;
        j=nx[fa[d[h]]];

        for (i=ls[d[h]]; i; i=a[i][1])
        d[++t]=a[i][0];

        fa[j]=d[h];
        nx[fa[d[h]]]=d[h];
        nx[d[h]]=j;

        Ans[++tot]=j;
    }

    for (i=1; i; i=nx[i])
    printf("%d ",i-1);
    printf("\n");
    printf("%d\n",tot);
    fd(i,tot,1)
    printf("%d ",Ans[i]-1);
}

E

题意:
给出一个数k和n个数(都不能整除k),每次可以把两个数合并,合并后的值除k直到不整除为止
求一种把n个数合并成1的方案
n<=16,k<=2000,∑ai<=2000

题解:
O(3^n*2000)的做法显然过不了
可以发现,每种合并的方案最终都可以表示为∑ai*k^bi,其中bi<0
证明每种∑ai*k^bi=1的情况都能对应一种合法方案:
①n=1
那么只有a1=1时才成立
(不存在a1>1的情况)
②n>1
设序列中最小的bi为B,那么必定存在至少两个bi=B
证明:
若只有一个,那么
∑ai*k^bi=1
∑ai*k^(bi-B)=k^(-B)
当bi>B时,乘积必为k的倍数,而右侧也为k的倍数
如果只有一个bi=B,且ai%k≠0,左右在模k意义下不等,实际也必然不等

那么每次把bi=bj=B的ij合并,把新的数的b变为(b+合并后的数除k的次数),合并的数变为f(i+j)即可
可以发现这样仍满足∑ai*k^bi=1,而n-1的所有情况都已经归纳证明了
③n=2
由于n=1比较特殊,所以n=2也要特殊考虑
从n>1的结论得知,b1=b2=B
因为a1*k^B+a2*k^B=1
a1+a2=k^(-B)
所以f(a1+a2)=1,可以变为n=1的情况,即上文所说不存在a1>1



那么dp就很显然了,考虑每次操作加一个a,或者对于所有的a除以k
把一种合法的序列还原时,可以发现其中不存在小数
所以dp时的ai和也必为整数
用bitset优化,反着还原dp,然后正着求出每次操作
还原dp时不需要记录上个状态(因为有bitset),每次枚举一种操作判断原状态是否存在即可

code

#include <algorithm>
#include <iostream>
#include <cstdlib>
#include <cstring>
#include <cstdio>
#include <bitset>
#define fo(a,b,c) for (a=b; a<=c; a++)
#define fd(a,b,c) for (a=b; a>=c; a--)
using namespace std;

struct type{
    int a,b;
} b[17];
int p[17];
int a[17];
int ans[101];
bitset<2001> f[65536];
int n,K,L,i,j,k,l,sum,len,s,tot;

bool cmp(type a,type b)
{
    return a.b>b.b;
}

int main()
{
//  freopen("e.in","r",stdin);

    p[1]=1;
    fo(i,2,16)
    p[i]=p[i-1]<<1;

    scanf("%d%d",&n,&K);L=(p[n]<<1)-1;
    fo(i,1,n)
    scanf("%d",&a[i]),sum+=a[i];
    sum/=K;

    f[0][0]=1;
    fo(i,1,L)
    {
        fo(j,1,n)
        if (i&p[j])
        f[i]|=f[i^p[j]]<<a[j];

        fd(j,sum,1)
        if (f[i][j*K])
        f[i][j]=1;
    }

    if (!f[L][1])
    printf("NO\n");
    else
    {
        printf("YES\n");

        s=L;j=1;
        while (j)
        {
            if (j<=sum && f[s][j*K])
            {
                j*=K;
                ans[++len]=-1;
                continue;
            }

            fo(i,1,n)
            if (s&p[i] && j>=a[i] && f[s-p[i]][j-a[i]])
            {
                s-=p[i];
                j-=a[i];
                ans[++len]=i;

                break;
            }
        }

        j=0;
        fo(i,1,len)
        if (ans[i]==-1)
        ++j;
        else
        b[++tot]={a[ans[i]],-j};

        while (tot>1)
        {
            sort(b+1,b+tot+1,cmp);

            printf("%d %d\n",b[tot-1].a,b[tot].a);
            b[tot-1].a+=b[tot].a;

            --tot;
            while (!(b[tot].a%K))
            b[tot].a/=K,++b[tot].b;
        }
    }
}

原文地址:https://www.cnblogs.com/gmh77/p/11779812.html

时间: 2024-11-05 23:37:08

Codeforces Round #596 Div1 A~E题解的相关文章

Codeforces Round #285 Div1 A and Div2 C

Problem 给一个图G,保证G是一个森林(坑!).图G含有N个点,给出每个点的两个属性:度数(degree).异或和(sum).度数表示该点与多少个点相连,异或和表示与其相连的点的编号的异或和(点编号从0开始,若度数为0则异或和为0).要求求出原图,输出边的个数和每条边两端的顶点. Limits Time Limit(ms): 1000 Memory Limit(MB): 256 N: [1, 2^16] degree: [0, N-1] Solution 由于G是森林,G的每一个连通图一定

Codeforces Round #290 Div1 A

Problem 给N串字符串Si,通常定义字典序大小关系为 'a'<'b'<'c'<......<'y'<'z',现要求重新定义大小关系使得对于任意 i,j(i<j)满足Si <Sj,输出大小关系(一串'a'-'z'的排列),或者输出不存在(任意大小关系都不能满足要求). Limits Time Limit(ms): 2000 Memory Limit(MB): 256 N: 100 |Si|: 100 Solution 用图论方法解决,发现满足拓扑关系.枚举相邻

Codeforces Round #290 Div1 B

Problem 有一只青蛙在x轴上跳,起初在原点,现有N种跳跃技能可以购买,每技能有两属性:跳跃长度Li 以及 花费Ci.若购买了第 i 种技能,则可以从 y 位置跳跃到 y+Li 或者 y-Li 位置,但需花费Ci 元.求最小花费使得青蛙可以跳到每一个整数点上,若无法做到,输出-1. Limits Time Limit(ms): 2000 Memory Limit(MB): 256 N: 300 Li: [1, 10^9] Ci: [1, 10^5] Solution 若购买了n个属性使得青蛙

# Codeforces Round #529(Div.3)个人题解

Codeforces Round #529(Div.3)个人题解 前言: 闲来无事补了前天的cf,想着最近刷题有点点怠惰,就直接一场cf一场cf的刷算了,以后的题解也都会以每场的形式写出来 A. Repeating Cipher 传送门 题意:第一个字母写一次,第二个字母写两次,依次递推,求原字符串是什么 题解:1.2.3.4,非常明显的d=1的等差数列,所以预处理一个等差数列直接取等差数列的每一项即可 代码: #include<bits/stdc++.h> using namespace s

Codeforces Round #531 (Div. 3) ABCDE题解

Codeforces Round #531 (Div. 3) 题目总链接:https://codeforces.com/contest/1102 A. Integer Sequence Dividing 题意: 给一个数n,然后要求你把1,2.....n分为两个集合,使得两个集合里面元素的和的差的绝对值最小. 题解: 分析可以发现,当n%4==0 或者 n%3==0,答案为0:其余答案为1.之后输出一下就好了. 代码如下: #include <bits/stdc++.h> using name

Codeforces Round #540 (Div. 3) 部分题解

Codeforces Round #540 (Div. 3) 题目链接:https://codeforces.com/contest/1118 题目太多啦,解释题意都花很多时间...还有事情要做,就选一些题来写吧. B. Tanya and Candies 题意: 在n个数中任意删去一个,如果这个数被删去后,剩余数的奇数和以及偶数和相等,那么就定义这个数为"好数".现在问这n个数中有多少个“好数”. 题解: 预处理出奇数前缀和.偶数前缀和,删去一个数后所有的奇数位置和 就为前面的奇数和

[Codeforces Round #444 div1] C.DZY Loves Colors 【线段树】

题目链接:CF Round #444 div1 C 题目分析 这道题目是要实现区间赋值的操作,同时还要根据区间中原先的值修改区间上的属性权值. 如果直接使用普通的线段树区间赋值的方法,当一个节点表示的区间完全被要求修改的区间包含时,就直接打上赋值的标记然后 return .但是这样这个节点中每个位置原先的值不同,需要进行的属性权值修改也就不同,是不能直接实现的.如果我们在节点表示的区间被修改的区间包含时,并不直接打标记 return ,而是当节点表示的区间被修改的区间完全包含而且这个节点中的每个

Codeforces Round #632 (Div. 2) 部分题解

目录 Codeforces Round #632 (Div. 2) A. Little Artem B. Kind Anton C. Eugene and an array D. Challenges in school №41 F. Kate and imperfection Codeforces Round #632 (Div. 2) A. Little Artem 题意:略. 分析:构造这样的图形: BWW...W BWW...W BBB...B #include <bits/stdc++

Codeforces Round 596 题解

万幸的是终于碰上了一场上分好场. 不幸的是一开始差点不会 A. 万幸的是想了个不那么稳的结论过了 pretest. 不幸的是罚时很高,而且慌得一比. 万幸的是然后半个小时内把 B 和 C 码了. 不幸的是然后就只能看着排名一点一点掉了. 万幸的是最后 A 没被叉掉. 不幸的是我居然没敢去叉人. 万幸的是我就是叉了 10 个人排名也不会上涨超过 5. 不幸的是我每掉一名都会少涨两三分. 万幸的是我没去打隔壁的 ZR. 不幸的是我发现这场 ZR 我一题不会,打了就会掉分-- 2A 没仔细想,但是应该