2020CCPC长春站 A,D,F,J,K,L

A. Krypton

二进制枚举所有情况,找最大的即可。

#include <bits/stdc++.h>
using namespace std;
int n;
int cost[7]={1,6,28,88,198,328,648};
int extra[7]={8,18,28,58,128,198,388};
int normal[7]={10,60,280,880,1980,3280,6480};
int main()
{
    scanf("%d",&n);
    int ans=0;
    for(int i=0;i<(1<<7);++i)
    {
        int tmp=n;
        int res=0;
        int now=i;
        for(int j=0;j<7;++j)
        {
            if(now&1)
            {
                tmp-=cost[j];
                res+=extra[j]+normal[j];
            }
            now>>=1;
        }
        if(tmp<0)
        continue;
        ans=max(ans,res+tmp*10);
    }
    printf("%d\n",ans);
    return 0;
}

D. Meaningless Sequence

我们发现,ai=(ci的二进制下1的个数)。对于求和,我们只要找到对于包含i个1的情况有多少种即可,这是个组合数学问题。我们从最高位的1开始,让其为0,那么后面可以随便取,然后再到第二位1(0的部分可以跳过,因为在前面已经被取到了),这时候前面的第一位取1,然后后面随意取…一直加到最后即可。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod=1e9+7;
const int N=3e3+5;
char a[N];
ll c;
ll quickpow(ll a,ll b)
{
    ll res=1;
    while(b)
    {
        if(b&1)
        res=res*a%mod;
        b>>=1;
        a=a*a%mod;
    }
    return res;
}
ll C[N][N];
int main()
{
    for(int i=0;i<=3001;++i)
    C[i][0]=1;
    for(int i=1;i<=3001;++i)
    for(int j=1;j<=i;++j)
    C[i][j]=(C[i-1][j]+C[i-1][j-1])%mod;
    scanf("%s",a);
    scanf("%lld",&c);
    ll ans=0;
    ll now=0;
    int len=strlen(a);
    for(int i=0;i<len;++i)
    {
        if(a[i]=='1')
        {
            int up=len-i-1;
            for(int j=0;j<=up;++j)
            {
                ans=(ans+C[up][j]*quickpow(c,j+now)%mod)%mod;
            }
            now++;
        }
    }
    printf("%lld\n",(ans+quickpow(c,now))%mod);
    return 0;
}

F. Strange Memory

dsu on tree(有时间写篇博客总结一下),对于dsu on tree有两种类型的题,一种是直接算子树贡献,还有一种就和这题一样计算不同子树间的贡献。因为是要计算不同子树间的贡献,所以需要用到递归。然后本题可以用vector过,正解是要拆位,对于要计算异或和,我们将其拆为20位,对于每一位,记录0和1的个数,对于0来说,异或上1就会产生贡献。所以我们只要统计每位0和1的个数即可,这样时间复杂度为O(20nlogn)。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e5+5;
int n;
int a[N];
vector<int>g[N],temp;
int p[(1<<20)+5][20][2];
ll ans;
//ll now;
int siz[N],son[N];
int id_num[(1<<20)+5];
ll pow2[20];
void dfs(int x,int fa)
{
    siz[x]=1;
    son[x]=0;
    for(int i=0;i<g[x].size();++i)
    {
       int v=g[x][i];
       if(v==fa)
       continue;
       dfs(v,x);
       siz[x]+=siz[v];
       if(!son[x]||siz[v]>siz[son[x]])
       {
           son[x]=v;
       }
    }
}
//void get_data(int x,int op)
//{
//    for(int i=L[x];i<=R[x];++i)
//    {
//        if(reg[i]==skp)
//        {
//            i=R[reg[i]];
//            continue;
//        }
//        if(op!=-1)
//        {
//            int val=a[reg[i]]^op;
//            int len=p[val].size();
//            for(int j=0;j<len;++j)
//            {
//                ans+=p[val][j]^reg[i];
//            }
//            p[a[reg[i]]].push_back(reg[i]);
//        }
//        else
//        {
//            p[a[reg[i]]].pop_back();
//        }
//    }
//}
void cal(int x,int fa,int lca)
{
    temp.push_back(x);
    for(int i=0;i<20;++i)
    {
        ans+=1ll*p[a[x]^a[lca]][i][!((x>>i)&1)]*pow2[i];
    }
    for(int i=0;i<g[x].size();++i)
    {
        int v=g[x][i];
        if(v==fa)
        continue;
        cal(v,x,lca);
    }
}
void del(int x,int fa)
{
    if(id_num[a[x]])
    {
        memset(p[a[x]],0,sizeof(p[a[x]]));
        id_num[a[x]]=0;
    }
    for(int i=0;i<g[x].size();++i)
    {
        int v=g[x][i];
        if(v==fa)
        continue;
        del(v,x);
    }
}
void dsu(int x,int fa,bool keep)
{
    //cout<<x<<endl;
    for(int i=0;i<g[x].size();++i)
    {
        int v=g[x][i];
        if(v==fa||v==son[x])
        continue;
        dsu(v,x,0);
    }
    //cout<<x<<" "<<son[x]<<endl;
    if(son[x])
    {
        dsu(son[x],x,1);
    }
    for(int i=0;i<20;++i)
    p[a[x]][i][(x>>i)&1]++;
    id_num[a[x]]++;
    //cout<<x<<" "<<g[x].size()<<endl;
    for(int i=0;i<g[x].size();++i)
    {
        int v=g[x][i];
        //cout<<v<<" "<<x<<" "<<son[x]<<" "<<fa<<endl;
        if(v==fa||v==son[x])
        continue;
        cal(v,x,x);
        for(int j=0;j<temp.size();++j)
        {
            int it=temp[j];
            //int tmp=1;
            for(int k=0;k<20;++k)
            {
                p[a[it]][k][(it>>k)&1]++;
            }
            id_num[a[it]]++;
        }
        temp.clear();
    }
    if(!keep)
    {
        del(x,fa);
        //now=0;
    }
}
int main()
{
    pow2[0]=1;
    for(int i=1;i<20;++i)
    pow2[i]=pow2[i-1]<<1;
    scanf("%d",&n);
    for(int i=1;i<=n;++i)
    scanf("%d",&a[i]);
    for(int i=1;i<n;++i)
    {
        int x,y;
        scanf("%d%d",&x,&y);
        g[x].push_back(y);
        g[y].push_back(x);
    }
    dfs(1,0);
    dsu(1,0,1);
    printf("%lld\n",ans);
    return 0;
}

H. Combination Lock

待补

J. Abstract Painting

状压dp,只在x轴上,可以将其两边看成是左右扩号。因为圆的半径最多只有5,所以其左括号最多会对其后十个点产生影响,所以可以用状压dp求解。(这里我们将整个图右移一格)我们设dp[i][j]为以第i个点为右括号对于前10个点状态为j的方案数(其中1的位置表示其不能为左括号,0表示可以)。初始情况,dp[0][j]=0,因为图右移了一格,所以此数0的位置为负x轴,不能存在左括号,所以方案数为0。对于当前点的每种状态的dp值,若能由上一个点的dp转移到,其必须满足,当前状态和之前状态要相应,即之前状态不能为左括号的位置,当前状态就不能在那个位置取左括号,(因此之前状态和当前状态相与是0才满足条件),还有因为之前图上是有圆的,所以还要保证当前状态得满足图上的圆,所以图上圆的状态和当前状态相与得是1,因为如果图上圆的左括号取到与当前圆的左括号相同位置时,就有两个交点了,所以要取到当前状态不能取左括号的地方。因为我们只是枚举了,当前位置为右括号的状态,所以其实际状态实际是其最大扩号的状态或上之前位置的状态再向左移位一次(因为对于后面的点来说,当前点相切也可以,所以可以取左括号)。

#include <bits/stdc++.h>
using namespace std;
int n,k;
int pos[32];
int info[32];
int maxstate=(1<<10)-1;
int dp[1010][(1<<10)];//dp[i][j]代表到第i个位置,[i-9,i]是否被圆包含的二进制状态为(j)2的方案数
//二进制信息(j)2的第k位为1,代表点i-k被包含(不能作圆的左括号),反而反之。
vector<int>v[1010];
const int mod=1e9+7;
int main()
{
    scanf("%d%d",&n,&k);
    for(int i=1;i<=k;++i)
    {
        int x,r;
        scanf("%d%d",&x,&r);
        v[x+1+r].push_back(2*r);
    }
    memset(info,-1,sizeof(info));
    for(int i=1;i<32;++i)
    {
        int tmp=0;
        for(int j=4;j>=0;j--)
        {
            if((i>>j)&1)
            {
                tmp|=(1<<(j*2+1));
                if(info[i]==-1)
                {
                    info[i]=(1<<(j*2+1))-1;
                }
            }
        }
        pos[i]=tmp;
    }
    pos[0]=info[0]=0;
    dp[0][maxstate]=1;
    for(int i=1;i<=n+1;++i)
    {
        for(int j=0;j<=maxstate;++j)
        {
            for(int k=0;k<32;++k)
            {
                int flag=0;
                for(int p=0;p<v[i].size();++p)
                {
                    int r=v[i][p];
                    if(!(pos[k]&(1<<r-1)))
                    {
                        flag=1;
                    }
                }
                if(flag||(j&pos[k]))
                {
                    continue;
                }
                int now=((j|info[k])<<1)&maxstate;
                dp[i][now]+=dp[i-1][j];
                dp[i][now]%=mod;
            }
        }
    }
    int ans=0;
    for(int i=0;i<=maxstate;++i)
    {
        ans+=dp[n+1][i];
        ans%=mod;
    }
    printf("%d\n",ans);
    return 0;
}

K. Ragdoll

首先,我们看这个式子 gcd(x,y)=x⊕y。我们要获得每一个满足式子的点对,暴力做法是O(n2)的,肯定不行。我们对式子进行调整,两边同时异或x,该式子变为 x⊕gcd(x,y)=y。因为是gcd(x,y),所以其一定是x的某个因子,所以我们确定一个值为gcd(x,y),然后对于其每个倍数为x,我们可以找到一个y,再将这些带到这个式子判断一下满不满足条件即可,这样就可以O(nlognlongn)的找到所有点对了。然后对于合并,按秩合并即可(总复杂度nlogn)。剩下操作暴力完成即可(加点O(1),修改值也是O(1)的因为对于一个值,也就几十个与其符的点对)。

#include <bits/stdc++.h>
using namespace std;
const int N=2e5+5;
const int M=3e5+5;
typedef long long ll;
int gcd(int x,int y)
{
    return y==0?x:gcd(y,x%y);
}
vector<int>g[N];
map<int,int>mp[M];
vector<int>s[M];
int fa[M];
int siz[M];
int n,m;
int a[M];
ll ans;
int find(int x)
{
    if(x==fa[x])
    return x;
    else
    return fa[x]=find(fa[x]);
}
void add_point(int x,int v)
{
    fa[x]=x;
    a[x]=v;
    s[x].push_back(x);
    siz[x]=1;
    mp[x][v]=1;
}
void merge(int x,int y)
{
    int fa_x=find(x);
    int fa_y=find(y);
    if(fa_x==fa_y)
    return ;
    if(siz[fa_x]>siz[fa_y])
    swap(fa_x,fa_y);
    for(int i=0;i<s[fa_x].size();++i)
    {
        int x=a[s[fa_x][i]];
        for(int j=0;j<g[x].size();++j)
        {
            int y=g[x][j];
            ans+=mp[fa_y][y];
        }
    }
    for(int i=0;i<s[fa_x].size();++i)
    {
        int x=s[fa_x][i];
        s[fa_y].push_back(x);
        mp[fa_y][a[x]]++;
    }
    siz[fa_y]+=siz[fa_x];
    fa[fa_x]=fa_y;
}
void update(int x,int v)
{
    int fa_x=find(x);
    for(int i=0;i<g[a[x]].size();++i)
    {
        int y=g[a[x]][i];
        ans-=mp[fa_x][y];
    }
    //cout<<ans<<endl;
    mp[fa_x][a[x]]--;
    a[x]=v;
    for(int i=0;i<g[v].size();++i)
    {
        int y=g[v][i];
        ans+=mp[fa_x][y];
        //cout<<y<<" "<<mp[fa_x][y]<<endl;
    }
    mp[fa_x][v]++;
}
int main()
{
    for(int i=1;i<=200000;++i)
    for(int j=i+i;j<=200000;j+=i)
    {
        if(gcd(j,i^j)==i)
        g[j].push_back(i^j);
    }
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;++i)
    {
        scanf("%d",&a[i]);
        fa[i]=i;
        mp[i][a[i]]=1;
        siz[i]=1;
        s[i].push_back(i);
    }
    int t,x,y,v;
    while(m--)
    {
        scanf("%d",&t);
        if(t==1)
        {
            scanf("%d%d",&x,&v);
            add_point(x,v);
        }
        else if(t==2)
        {
            scanf("%d%d",&x,&y);
            merge(x,y);
        }
        else
        {
            scanf("%d%d",&x,&v);
            update(x,v);
        }
        printf("%lld\n",ans);
    }
    return 0;
}

L. Coordinate Paper

构造题。首先,如果我们确定了a1,我们就能确定当前序列的最小情况,即a1,a1+1,a1+2,…,k,0,…0,1,2,…k,0,1…。我们可以O(1)求出其和,这样我们知道其和%(k+1)是多少,并且如果要修改序列,其%(k+1)的值是不变的,所以,我们断言如果这个序列能构造出s,其必须满足,其最小值的和%(k+1)与s%(k+1)是一样的且s>=其最小值的和。这样我们可以O(k)的找到a1。找到之后要如何构造呢,就是先对0的位置加(k+1),再对1的位置加(k+1)…即可(试一下就知道其正确性,这样做是肯定合法的)。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e5+5;
ll n,k,s;
ll ans[N];
struct node
{
    ll val;
    int id;
}tmp[N];
bool cmp(node a,node b)
{
    return a.val<b.val;
}
ll get_ar(ll a)
{
    return ((a+1)*a)>>1;
}
ll get_sum(ll a,ll b)
{
    ll sum=0;
    ll up=k-a+1;
    if(b<up)
    {
        return get_ar(a+b-1)-get_ar(a-1);
    }
    else
    {
        sum+=get_ar(k)-get_ar(a-1);
    }
    ll num=(b-up)/(k+1);
    ll num2=(b-up)%(k+1);
    sum+=get_ar(k)*num;
    sum+=get_ar(num2-1);
    return sum;
}
void construct_ans(ll pos)
{
    ll num=(s-get_sum(pos,n))/(n*(k+1));
    ll num2=(s-get_sum(pos,n))%(n*(k+1));
    num2=num2/(k+1);
    ans[1]=pos;
    tmp[1].id=1;
    tmp[1].val=pos;
    for(int i=2;i<=n;++i)
    {
        ans[i]=(ans[i-1]+1)%(k+1);
        tmp[i].id=i;
        tmp[i].val=ans[i];
    }
    sort(tmp+1,tmp+1+n,cmp);
    for(ll i=1;i<=num2;++i)
    {
        ans[tmp[i].id]+=(k+1);
    }
    for(int i=1;i<=n;++i)
    {
        ans[i]+=num*(k+1);
    }
}
int main()
{
    scanf("%lld%lld%lld",&n,&k,&s);
    int pos;
    bool flag=0;
    for(int i=0;i<=k;++i)
    {
        ll a1=i;
        if((s%(k+1)==get_sum(a1,n)%(k+1))&&s>=(get_sum(a1,n)))//可以找到解
        {
            flag=1;
            pos=i;
            break;
        }
    }
    if(!flag)
    puts("-1");
    else//构造答案
    {
        construct_ans(pos);
        for(int i=1;i<=n;++i)
        printf("%lld ",ans[i]);
        puts("");
    }
    return 0;
}

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值