2022 China Collegiate Programming Contest (CCPC) Guilin Site 2022CCPC 桂林 (7/13)

A 签到

C 推式子,思维,int128

E 几何,扩展欧几里得

G 树形dp,图论结论 ,记忆化搜索

J 拓扑排序 ,贪心构造】

M 树状数组求逆序对,双端队列模拟

L 博弈纳什平衡

 Problem - A - Codeforces

签到,没啥说的

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int vis[1005];
int main()
{
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    int n;
    cin >> n;
    string str;
    cin >> str;
    str = "?" + str;
    for (int i = 1; i <= n; i++)
    {
        if (str[i] == 'L')
            vis[i - 1] = vis[i] = vis[i + 1] = 1;
    }
    for (int i = 1; i <= n; i++)
    {
        if (vis[i])
            cout << str[i];
        else
            cout << "C";
    }
    return 0;
}

 Array Concatenation

手推不难发现,只要是进行一次翻转操作,之后无论进行任何操作,得到的结果都是一样的。所以我们可以枚举第几次使用了翻转操作。我们现在需要获取的是,翻转操作之前数组的各项情况,再把他当成一个新的数组来求最终的结果。画图会好理解一些。注意开int128,否则会爆炸

#include <bits/stdc++.h>

using namespace std;
typedef __int128 ll;
# define mod 1000000007

inline __int128 read()
{
    __int128 x=0,f=1;
    char ch=getchar();
    while(ch<'0'||ch>'9')
    {
        if(ch=='-')
            f=-1;
        ch=getchar();
    }
    while(ch>='0'&&ch<='9')
    {
        x=x*10+ch-'0';
        ch=getchar();
    }
    return x*f;
}

inline void write(__int128 x)
{
    if(x<0)
    {
        putchar('-');
        x=-x;
    }
    if(x>9)
        write(x/10);
    putchar(x%10+'0');

}
ll a[1000000+10],sum[1000000+10];
ll bac[1000000+10],bacs;
ll cnt[1000000+10];
ll inv;

ll qp(ll base, ll pow)
{
    ll ans=1;
    while(pow)
    {
        if(pow&1)
            ans=ans*base%mod;
        pow>>=1;
        base=base*base%mod;

    }

    return ans;
}
ll getsum(ll b,ll e,ll xiang)
{

    b+=mod;
    b%=mod;
    e+=mod;
    e%=mod;
    xiang+=mod;
    xiang%=mod;
    return (b+e)%mod*xiang%mod*inv%mod;
}
int main()
{

    inv=qp(2ll,mod-2);

    ll n,m;
    n=read();
    m=read();
    ll s=0;
    for(ll i=1;i<=n;i++)
    {
      a[i]=read();
        sum[i]=sum[i-1]+a[i];
        sum[i]%=mod;
        s+=sum[i];
        s%=mod;
    }

    cnt[0]=1;

    for(ll i=1;i<=1000000;i++)
    {
        cnt[i]=cnt[i-1]*2ll%mod;
    }

    for(int i=n;i>=1;i--)
    {
        bac[i]=bac[i+1]+a[i];
        bac[i]%=mod;
        bacs+=bac[i];
        bacs%=mod;
    }
    ll ans=0;
    for(ll i=1;i<=m+1;i++)
    {
       ll nowcnt=cnt[i-1]%mod;
       ll temppre=nowcnt*s%mod;
       ll tempbac=nowcnt*bacs%mod;
       ll fuck=getsum(n*sum[n]%mod,(nowcnt-1+mod)*n%mod*sum[n]%mod,nowcnt-1);
       ll fuckbac=getsum(n*bac[1]%mod,(nowcnt-1)*n%mod*bac[1]%mod,nowcnt-1);
       fuck+=temppre%mod;
       fuck%=mod;
       if(i==m+1)
       {
           ans=max(ans,fuck);
           break;
       }
       fuckbac+=tempbac%mod;
       fuckbac%=mod;
       ll nexcnt=cnt[m-i];
       ll nown=cnt[i-1]*n%mod;
       ll sumbac=fuckbac*nexcnt%mod;
       ll sumpre=fuck*nexcnt%mod;
       tempbac=cnt[i-1]*bac[1]%mod;
       temppre=cnt[i-1]*sum[n]%mod;
       ll bacbac=getsum(nown*tempbac%mod,(nexcnt-1)*nown*tempbac%mod,(nexcnt-1)%mod);
       bacbac*=2ll;
       bacbac%=mod;
       ll prepre=getsum(nown*temppre%mod,(nexcnt-1)*nown*temppre%mod,(nexcnt-1)%mod);
       prepre*=2ll;
       prepre%=mod;
       bacbac+=nexcnt*nown%mod*tempbac%mod;
       bacbac%=mod;
       if(i==1)
       ans=max(ans,((sumbac+sumpre)%mod+(bacbac+prepre)%mod)%mod);

    }
    write(ans);
    return 0;

}

 Draw a triangle

需要一点向量叉乘求三角形面积的知识,有(x1,y1) (x2,y2),另有 (x3,y3)未知

AB (x1-x2.y1-y2)

AC (x1-x3,y1-y3)

1/2 * (x1-x2)*(y1-y3) + (x1-x3)*(-y1+y2) 

然后已知的作为x,y,未知的作为系数a,b,一次扩欧就行

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

ll exgcd(ll a, ll b,ll &x,ll&y)
{
    if(b==0)
    {
        y=0;
        x=1;
        return a;
    }
    ll temp=exgcd(b,a%b,x,y);
    ll yy=y;
    y=x-a/b*y;
    x=yy;
    return temp;

}
int main()
{

    int t;
    cin>>t;
    while(t--)
    {
        ll x1,y1,x2,y2;
        cin>>x1>>y1>>x2>>y2;
        ll a=x2-x1;
        ll b=y1-y2;
        ll x,y;
        exgcd(a,b,x,y);
        x+=y1;
        y+=x1;

        cout<<y<<" "<<x<<endl;
    }

    return 0;
}

Group Homework

两种情况,一种没有交点,也就是两条链分居在某一条边的两侧。一种是有交点,画图分析得,只有一个焦点的时候最优,进一步转化为一个点连接的四条链最大值。

很多人用树形dp,换根去做,其实推起来很复杂。不如利用树确定父亲以及儿子便可以决定今后遍历顺序这一性质,外加父亲儿子关系不超过2*n这一条件,直接记忆化。

先爆搜每个点连接的四条链,取max, 再断开每一条边分别求两侧链条最大值。这一过程可以合并一个点连接的两条链,也可以取更深一层搜索获得的链,二者取最大值即可

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

unordered_map<int,int>mp[200000+10];
int a[1000000+10];
vector<int>v[200000+10];
int x[200000+10],y[200000+10];
vector<int>maxx[200000+10];
inline int dfs1(int now,int pre)
{
    if(mp[now][pre])
        return mp[now][pre];
    int nowans=0;
    for(auto it:v[now])
    {
        if(it==pre)
            continue;
        int temp=dfs1(it,now)+a[it];
        nowans=max(nowans,temp);
        if(pre==0)
            maxx[now].push_back(temp);
    }
    return mp[now][pre]=nowans;
}
bool cmp(int x,int y)
{
    return x>y;
}
unordered_map<int,int>dp2[200000+10];
 inline int dfs2(int now,int pre)
{
    if(dp2[now][pre])
        return dp2[now][pre];
    int maxx1=0,maxx2=0,maxx3=0;
    for(auto it:v[now])
    {
        if(it==pre)
            continue;
        int temp=dfs1(it,now)+a[it];
        if(temp>maxx1)
        {
            maxx2=maxx1;
            maxx1=temp;
        }
        else
        {
            maxx2=max(maxx2,temp);
        }
        maxx3=max(maxx3,dfs2(it,now));
    }
    return dp2[now][pre]=max(maxx3,maxx1+maxx2+a[now]);
}
int main()
{
    int n;
    cin>>n;
    for(int i=1; i<=n; i++)
    {
        cin>>a[i];
    }
    for(int i=1; i<n; i++)
    {
        cin>>x[i]>>y[i];
        v[x[i]].push_back(y[i]);
        v[y[i]].push_back(x[i]);
    }
    int ans=0;
    for(int i=1; i<=n; i++)
    {
        dfs1(i,0);
        sort(maxx[i].begin(),maxx[i].end(),cmp);
        int cnt=0,now=0;
        for(auto it:maxx[i])
        {
            cnt++;
            if(cnt>4)
                break;
            now+=it;
        }
        ans=max(ans,now);
    }
    for(int i=1; i<n; i++)
    {
        ans=max(ans,dfs2(x[i],y[i])+dfs2(y[i],x[i]));
    }
    cout<<ans;
    return 0;
}

 Permutation Puzzle

首先大小关系是可以利用拓扑排序的次序来实现的。严格按照小指向大的方式连边,获取每个位置最小值的时候,拓扑序上每次取max。获取最大值的时候,建立反图,每次取min。详见代码。这样我们获得了取值的[L,R]。现在就变成了一个经典贪心问题。考虑从1-n开始构造答案。按照左端点进行排序,每次把<=i的全部左端点的右端点都加入set。贪心的想,我们一定是把当前右端点最小的给赋值,因为更大一些的可以在i后面被赋到。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

vector<int>v1[200000+10],v2[200000+10];
int du1[200000+10],du2[200000+10],L[200000+10],R[200000+10],a[200000+10];
int dp1[200000+10],dp2[200000+10];
bool cmp(int x,int y)
{
    return L[x]<L[y];
}
int ans[200000+10],b[200000+10];
int main()
{
    int t;
    cin>>t;
    while(t--)
    {
        int n,m;
        cin>>n>>m;
        for(int i=1; i<=n; i++)
        {
            du1[i]=du2[i]=0;
            v1[i].clear();
            v2[i].clear();
            cin>>a[i];
            if(a[i])
            {
                L[i]=a[i];
                R[i]=a[i];
            }
            else
            {
                L[i]=1;
                R[i]=n;
            }
        }
        for(int i=1; i<=m; i++)
        {
            int x,y;
            scanf("%d%d",&x,&y);
            v1[x].push_back(y);
            v2[y].push_back(x);
            du1[y]++;
            du2[x]++;
        }
        queue<int>q;
        for(int i=1; i<=n; i++)
        {
            if(du1[i]==0)
            {
                q.push(i);
            }
        }
        while(!q.empty())
        {
            int now=q.front();
            q.pop();
            for(auto it:v1[now])
            {
                du1[it]--;
                L[it]=max(L[it],L[now]+1);

                if(du1[it]==0)
                {
                    q.push(it);
                }
            }
        }
        for(int i=1; i<=n; i++)
        {
            if(du2[i]==0)
            {
                q.push(i);
            }

        }
        while(!q.empty())
        {
            int now=q.front();
            q.pop();
            for(auto it:v2[now])
            {
                du2[it]--;

                R[it]=min(R[it],R[now]-1);

                if(du2[it]==0)
                {
                    q.push(it);
                }
            }
        }
        int flag=0;
        set<pair<int,int>>s;
        for(int i=1; i<=n; i++)
        {
            b[i]=i;
        }
        sort(b+1,b+1+n,cmp);
        int nowpos=1;
        for(int i=1; i<=n; i++)
        {
            while(nowpos<=n&&L[b[nowpos]]<=i)
            {
                s.insert(make_pair(R[b[nowpos]],b[nowpos]));
                nowpos++;
            }
            if(s.empty())
            {
                flag=1;
                break;
            }
            auto it=*s.begin();
            if(it.first<i)
            {
                flag=1;
                break;
            }
            ans[it.second]=i;
            s.erase(s.begin());

        }
        if(flag)
        {
            cout<<-1<<'\n';
        }
        else
        {
            for(int i=1;i<=n;i++)
            {
                if(ans[i]!=a[i]&&a[i])
                {
                    flag=1;
                    break;
                }
            }
            if(flag)
            {
                cout<<-1<<endl;
                continue;
            }
            for(int i=1; i<=n; i++)
            {
                cout<<ans[i]<<" ";
            }
            cout<<'\n';
        }
    }
    return 0;
}

 Youth Finale

第一个答案就是逆序对个数,直接树状数组。

然后就是每次把首部移动到尾部,实质就是把小于首部的数量减去,加上大于首部的

翻转操作就是逆序对x变成n*n(-1)/2-x,然后每次把双端队列尾部移动要首部即可。

注意long long 

#include <bits/stdc++.h>

using namespace std;
typedef long long int ll;
# define mod 10
ll n,m;
ll sum[300000+10];
deque<ll>d;
ll lowbit(ll x)
{
    return x&-x;
}
void change(ll pos)
{
    while(pos<=n)
    {
        sum[pos]++;
        pos+=lowbit(pos);
    }
}
ll getsum(ll pos)
{
    ll ans=0;
    while(pos)
    {
        ans+=sum[pos];
        pos-=lowbit(pos);
    }
    return ans;
}
ll a[300000+10];
ll fuck[1000000+10];
string s;
int main()
{
    cin>>n>>m;
    for(ll i=1; i<=n; i++)
    {
        scanf("%lld",&a[i]);
        d.push_back(a[i]);
    }
    ll ans=0;
    for(ll i=n; i>=1; i--)
    {
        ans+=getsum(a[i]-1);
        change(a[i]);
    }
    cin>>s;
    cout<<ans<<endl;
    ll flag=0;
    ll temp=n*(n-1)/2;
    temp%=mod;
    for(int i=0; i<s.length(); i++)
    {
        if(s[i]=='R')
        {
            flag^=1;
            ans=temp-ans;
            ans%=mod;
            ans+=mod;
            ans%=mod;
        }
        else
        {
            if(flag==0)
            {
                int now=d.front();
                ans-=(now-1);
                ans%=mod;
                ans+=mod;
                ans%=mod;
                ans+=(n-now);
                ans%=mod;
                ans+=mod;
                ans%=mod;
                d.pop_front();
                d.push_back(now);
            }
            else
            {
                int now=d.back();
                ans-=(now-1);
                ans%=mod;
                ans+=mod;
                ans%=mod;
                ans+=(n-now);
                ans%=mod;
                ans+=mod;
                ans%=mod;
                d.pop_back();
                d.push_front(now);
            }
        }
        fuck[i]=ans%mod;
    }
    for(int i=0; i<m; i++)
    {
        fuck[i]%=mod;
        fuck[i]+=mod;
        fuck[i]%=mod;
        cout<<fuck[i];
    }
    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

秦三码

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值