Processing math: 100%

寒假训练(复健)记录

 

在vjudge上打了一场最近的edu的virtual

写了ABCD

E来不及看了,感觉有思路

太久没写代码,稳定性好差(

 

Educational Codeforces Round 121 (Rated for Div. 2)

A. Equidistant Letters

 

Problem - 1626A - Codeforces

发现距离可以是0,于是直接排序输出即可

代码不贴了(
B. Minor Reduction

Problem - B - Codeforces

直接贪心即可

优先保证位数,然后如果无法保证位数则优先令高位变大

保证位数的情况下优先让高位的变大

复制代码
#include <bits/stdc++.h>
using namespace std;
string ss;
int a[505];
int que1[505],que2[505],cnt1,cnt2,T;
int main(){
    cin>>T;
    while (T--){
        cin>>ss;
        int Len=ss.length();
        int pos=-1;
        bool flag=false;
        for (int i=0;i<Len-1;i++){
            int x=ss[i]-'0',y=ss[i+1]-'0';
            if (x+y>=10){
                pos=i;
                int sum=x+y;
                sum/=10;
                if (sum>x){
                        flag=true;            
                        for (int j=0;j<i;j++){
                        printf("%c",ss[i]);
                        printf("%d",x+y);
                        for (int j=i+2;j<Len;j++)
                        printf("%c",ss[i]);
                        printf("\n");
                        break;
                    }
                }
            }
        }
        //cout<<pos<<endl;
        if (!flag&&pos!=-1){
            for (int i=0;i<pos;i++)    printf("%c",ss[i]);
            int x=ss[pos]-'0',y=ss[pos+1]-'0';
            printf("%d",x+y);
            for (int i=pos+2;i<Len;i++)
                printf("%c",ss[i]);
            printf("\n"); 
        }
        if (!flag&&pos==-1){
            printf("%d",ss[0]-'0'+ss[1]-'0');
            for (int i=2;i<Len;i++)
                printf("%c",ss[i]);
            printf("\n");
        }
    }
    return 0;
}
复制代码

C. Monsters And Spells

画画图发现本质上是个线段合并问题

直接排序瞎写写即可

记得开long long

复制代码
#include <bits/stdc++.h>
using namespace std;
int N,T;
long long k[505],h[505];
pair<long long,long long> a[505];
int main(){
    cin>>T;
    while (T--){
        long long ans=0;
        scanf("%d",&N);
        for (int i=1;i<=N;i++) scanf("%lld",&k[i]);
        for (int i=1;i<=N;i++) scanf("%lld",&h[i]);
        for (int i=1;i<=N;i++){
        pair<long long,long long> Now;
        long long L=k[i]-h[i],R=k[i];
        L=max(L,0ll);
        Now.first=L;
        Now.second=R;
        a[i]=Now;
        }
        sort(a+1,a+N+1);
        long long l=a[1].first,r=a[1].second;
        for (int i=2;i<=N;i++){
            if (r<=a[i].first){
            ans+=1ll*(1ll+r-l)*(r-l)/2ll,l=0,r=0;
            }
            if (!l&&!r) {l=a[i].first,r=a[i].second; continue;}
            if (a[i].first>=l&&a[i].second<=r) continue;
            if (r<a[i].second) r=a[i].second;
        }
        ans+=1ll*(1ll+r-l)*(r-l)/2ll;
        cout<<ans<<endl;
    }
}
复制代码

D. Martial Arts Tournament

发现N2105

220>2105

于是可以暴力枚举一下每一段有几个人

然后贪心的去填就可以了。

O(NK)其中K是常数

复制代码
#include <bits/stdc++.h>
using namespace std;
int T,N;
int cnt[200005],a[200005];
int Calc(int x,int y){
    int sum1=0;
    int i;
    for (i=1;i<=N;i++){
        if (sum1+cnt[i]>x) break;
        sum1+=cnt[i];
    }
    int sum2=0;
    for (;i<=N;i++){
        if (sum2+cnt[i]>y) break;
        sum2+=cnt[i]; 
    }
    int sum3;
    sum3=N-sum1-sum2;
    for (i=0;(1<<i)<sum3;i++);
    return (x+y+(1<<i)-(sum1+sum2+sum3));
}
int main(){
    scanf("%d",&T);
    while (T--){
        scanf("%d",&N);
        for (int i=1;i<=N;i++){
            scanf("%d",&a[i]);
            cnt[a[i]]++;
        }
        int ans=1e9;
        for (int i=0;i<=20;i++)
            for (int j=0;j<=20;j++)
                ans=min(ans,Calc(1<<i,1<<j));
        for (int i=1;i<=N;i++)
            cnt[a[i]]=0;
        printf("%d\n",ans); 
    }
    return 0;
}
复制代码

E和F明天再说.jpg

Day 2

Codeforces Round #657 (Div. 2)

A. Acacius and String

Problem - A - Codeforces

直接模拟

代码注意一下细节

复制代码
#include <bits/stdc++.h>
using namespace std;
int T;
string ss;
int main(){
    cin>>T;
    string st="abacaba";
    int Len1=st.length();
    while (T--){
        string ss;
        int N;
        cin>>N;
        cin>>ss;            
        bool flag2=false;
        int Len=ss.length();
        for (int i=0;i<Len;i++){
            string s=ss;
            int Len=s.length();
            bool flag=false;
            for (int j=0;j<Len1;j++){
                if (s[i+j]=='?') s[i+j]=st[j];
                else if (s[i+j]!=st[j]) {flag=true;break;}
            }
            if (flag) continue;
            else {
                int cnt=0;
                for (int i=0;i<Len;i++)
                    if (s[i]=='?') s[i]='z';
                for (int i=0;i+Len1-1<Len;i++){
                bool flag1=false;
                    for (int j=0;j<Len1;j++)
                        if (s[i+j]!=st[j]) {flag1=true;break;}
                    if (!flag1) cnt++;
                }
                //cout<<s<<" "<<cnt<<endl;
                if (cnt==1){
                    printf("Yes\n");
                    cout<<s<<endl;
                    flag2=true;
                    break;
                }
            }
        }
        if (!flag2) printf("No\n");
    }
    return 0;
}
复制代码

B. Dubious Cyrpto

Problem - B - Codeforces

 

考虑移项

n=m+cba

又由于cb的范围是lrrl

于是其实只要在这个范围内有一个整数就行

也就是nmaxnmin>=1

处理一下即可

复制代码
#include <bits/stdc++.h>
using namespace std;
long long T,l,r,m;
int main(){
    cin>>T;
    while(T--){
        cin>>l>>r>>m;
        bool flag=false;
        for (int i=l;i<=r;i++){
            long long x=(m+l-r+i-1)/i,y=(m+r-l)/i;
            x=max(x,1ll);
            if (x<=y){
                flag=true;
                int t=m-i*x;
                if (t<0)
                printf("%lld %lld %lld\n",i,l,l-t);
                else printf("%lld %lld %lld\n",i,l+t,l);
            }
            if (flag) break;
        }
    }
    return 0;
}
复制代码

C. Choosing flowers

Problem - C - Codeforces

先考虑如果把所有的a全部买完要怎么做

显然剩下的是全部取最大的b

现在考虑钦定

钦定"剩下的"所有花是买某个bi

于是在买这个bi之前,我们贪心的想

肯定会先把比它大的ai和它本身的ai买掉

然后剩下的所有的花的数量买这个类型的bi

于是每次二分找出这个biai中的位置,前缀和后就是O(1)计算

总复杂度O(nlogn)

复制代码
#include <bits/stdc++.h>
using namespace std;
int T;
long long N,M;
struct Node{
    long long a,b;
}a[100005];
long long temp(Node x,Node y){
    return (x.a<y.a);
}
long long c[100005],Sum[100005];
int main(){
    cin>>T;
    while (T--){
        cin>>N>>M;
        for (int i=1;i<=M;i++) scanf("%d%d",&a[i].a,&a[i].b);
        sort(a+1,a+M+1,temp);
        long long ans=0,res=0;
        if (N>M)
        for (int i=1;i<=M;i++){
            ans+=a[i].a;
            res=max(res,a[i].b);        
        }
        else{
            int cntt=0;
            for (int i=M;cntt<N;i--,cntt++) ans+=a[i].a;
        }            
        ans=ans+1ll*(N-M)*res;    
        for (int i=1;i<=M;i++){
            c[i]=a[i].a;
            Sum[i]=Sum[i-1]+a[i].a;
        }
        for (int i=1;i<=M;i++){
            long long nw=a[i].b;
            long long x=lower_bound(c+1,c+M+1,nw)-c-1;
            if (M-x>N) continue;
            else{
                long long Ans=Sum[M]-Sum[x];
                long long nd=M-x;
                if (i<=x) Ans+=a[i].a,nd++;
                if (nd>N) continue;
                Ans+=1ll*(N-nd)*a[i].b;
                ans=max(ans,Ans);
            }
        }
        printf("%lld\n",ans);
    }
    return 0;
}
复制代码

DEF还没看

老年选手两小时只能写到C了

Day3


打了场ARC,然后心态崩了(

忘记开long long了C题挂了,到最后都没发现是long long的问题(

B二分dp最后都没调出来(
看了眼D发现会但是来不及了

A Erase by Value

考虑删除一个数字后的序列会不会更优,如果会的话就直接删

如果不会的话就继续找。

复制代码
#include <iostream>
#include <stdio.h>
using namespace std;
int N; 
int a[200005];
bool b[200005];
int main(){
    cin>>N;
    a[N+1]=-1;
    int X=-3;
    for (int i=1;i<=N;i++){
        scanf("%d",&a[i]);
    }
    for (int i=1;i<=N;i++){
        int x=a[i];
        while (a[i]==a[i+1]) i++;
        if (a[i+1]<x&&!b[a[i]]) {X=a[i];break;}
        b[a[i]]=true;
    }
    for (int i=1;i<=N;i++)
        if (a[i]==X) continue;
        else cout<<a[i]<<" ";
    return 0;
} 
复制代码

 

B - Dividing Subsequence

代码没调出来,大自闭(

a数组中每个位置在b中的合法位置找出来,因为题目给的是个排列,所以根据调和级数,这一定是nlogn级别的

然后问题就是给你a数组和对应的一堆位置,找一个最长上升的子序列。

求解最长上升子序列是有一个nlogn的二分dp做法的

又因为找最接近的上升数需要再二分一次

所以效率是nlog2n

代码还没调出来

C - Row Column Sums

显然全部填K1一定是最优的

现在加入限制

限制怎么满足?

对于每一行和每一列,先把它达到它的限制所需要调整的量算出来

这个用一个模意义下的加减法就可以处理

然后会发现,这个调整量的和同余才能调整是显然的(因为对行的改变也会同样的反应在相应的列上)

然后调整量小的是可以在调整量大的调整的时候顺便被调整的

所以调整量就是两个调整量中较大的那个

最后答案就是HW(K1)max(ans1,ans2)ans表示调整量的和。

复制代码
#include <bits/stdc++.h>
using namespace std;
long long K,H,W;
long long ans1,ans2,ans;
int main(){
    cin>>H>>W>>K;
    ans=1ll*H*W*1ll*(K-1ll);
    long long nd=1ll*W*(K-1ll)%K;
    for (int i=1;i<=H;i++){
        long long x;
        scanf("%lld",&x);
        ans1=ans1+((nd-x)+K)%K;
    }
    nd=1ll*H*(K-1)%K;
    for (int i=1;i<=W;i++){
        long long x;
        scanf("%lld",&x);
        ans2=ans2+(nd-x+K)%K;
    }
    if (ans1%K!=ans2%K) printf("-1");
    else{
        ans=ans-max(ans1,ans2);
        cout<<ans;
        return 0;
    }
}
复制代码

D - Range XOR


最后十几分钟看了一眼,口胡了个思路

先写着,错了再说吧(

题目考虑的是[l,r]之间的自然数连续异或和

因为异或的性质,我们可以拆成[1,l][1,r]的自然数连续异或和考虑

[l,r]之间的连续异或和就是[1,l]的异或和跟[1,r]的异或

然后自然数的连续异或和是有结论的

如果不知道的话打表也能发现

大概是这样:

t=nmod4

连续异或和的值为:

n t=0

1 t=1

n+1 t=2

0 t=3

那么对于值为01的部分,显然我们只需要考虑vxor0vxor1所产生的数字是否能由1情况和3情况产生即可

对于只由1情况和3情况构成的数

问题转化为如下问题:

xxory=v,且x0(mod4)x2(mod4)x[0,L] y[0,R]的无序数对(x,y)的数对个数

按照二进制位做一个类似于数位dp的东西就行了

还没写,如果写了挂了再说吧(

 

还是太久没做题了,代码水平和思维水平都好差……

CF1633D


发现bi的最大值才1000,要求的最少次数直接dp找就行了。

然后问题变成一个背包问题

然后发现K106级别没有意义,因为dp值最大也才12

于是和12n比较一下就行了。

复制代码
#include <bits/stdc++.h>
using namespace std;
int dp[1005],dp1[32005],ans;
const int mx=1000;
int T;
int N,K,b[1005],c[1005];
int main(){
    cin>>T;
    memset(dp,63,sizeof(dp));
    dp[1]=0;
    for (int i=1;i<=mx;i++)
        for (int j=1;j<=i;j++){
            int x=i+i/j;
            if (x<=mx)
            dp[x]=min(dp[x],dp[i]+1);
        }
    while (T--){
        cin>>N>>K;
        K=min(K,12*N);
        for (int i=1;i<=N;i++){
            int x;
            cin>>x;
            b[i]=dp[x];
        }
        ans=0;
        memset(dp1,0,sizeof(dp1));
        for (int i=1;i<=N;i++)
            cin>>c[i];
        for (int i=1;i<=N;i++)
            for (int j=K;j>=b[i];j--){
                dp1[j]=max(dp1[j],dp1[j-b[i]]+c[i]);
                ans=max(ans,dp1[j]);
        }
        cout<<ans<<endl;
    }
    return 0;
}
复制代码

CF1632D

套路,一段连续的gcd,随着区间长度的增加,它的gcd一定是递减的

而区间长度是递增的

于是这两个函数如果有交点一定只有一个交点

于是就对于每一个l位置,二分找一下是否有令它不合法的右端点

至于连续gcd的话,线段树随便写写就行了

如果有的话就存起来

然后考虑问题的转化

如果我修改区间中的一个数字为一个没有出现过的素数,那么这个区间的gcd一定是1

这样的话问题就变成了

对于一堆线段,每条线段上都要取其中一个点,问你最少取几个点

这就是一个经典的贪心问题

然后题目要的是前缀

我们离线一下,根据r排个序

然后每次做的时候把答案标记在r上,只有r右侧是这个答案

然后扫一遍区间输出即可。

复制代码
#include<bits/stdc++.h>
using namespace std;
struct Node{
    int l,r;
}a[200005];
int Tree[800005],N,cnt,ans[200005];
int aa[200005];
bool temp(Node a,Node b){
    return (a.r<b.r); 
}
void Build(int nw,int l,int r){
    if (l==r){
        Tree[nw]=aa[l];
        return;
    }
    int mid=(l+r)>>1;
    Build(nw<<1,l,mid);
    Build(nw<<1|1,mid+1,r);
    Tree[nw]=__gcd(Tree[nw<<1],Tree[nw<<1|1]);
    return;
}
int query(int Now,int L,int R,int l,int r){
    if (L<=l&&r<=R) return Tree[Now]; 
    int mid=(l+r)>>1;
    if (L<=mid&&mid<R) return __gcd(query(Now<<1,L,R,l,mid),query(Now<<1|1,L,R,mid+1,r));
    else if (L<=mid) return query(Now<<1,L,R,l,mid);
    else if (mid<R) return query(Now<<1|1,L,R,mid+1,r);
}
void Pre(){
    for (int i=1;i<=N;i++){
        int l=i,r=N+1;
        while (l<=r){
            int mid=(l+r)>>1;
            if (query(1,i,mid,1,N)>mid-i+1) l=mid+1;
            else if (query(1,i,mid,1,N)==mid-i+1){
                a[++cnt].r=mid;
                a[cnt].l=i;
                break;
            }
            else r=mid-1;
        }
    }
}
int main(){
    scanf("%d",&N);
    for (int i=1;i<=N;i++)
        scanf("%d",&aa[i]);
    Build(1,1,N);    
    Pre();
    sort(a+1,a+cnt+1,temp);
    int x=a[1].r,Ans=1;
    ans[a[1].r]=Ans;
    for (int i=1;i<=cnt;i++)
        if (a[i].l<=x) continue;
        else{
            x=a[i].r;
            Ans++;
            ans[a[i].r]=Ans;
    }
    int nw=0;
    for (int i=1;i<=N;i++){
        if (ans[i]!=0&&ans[i]!=nw) nw=ans[i];
        printf("%d ",nw);
    }
    return 0;
}
复制代码

 

CF1632E


思路歪了没做出来(
麻了.jpg

这种最大值最小的问题先考虑能不能二分

假装二分出一个答案ans,发现其实只要所有的depth大于ans的点中,距离最远的两个点的距离除以2加上当前枚举的边权xans小就可以了(类似树的直径,但是这里的树只有一部分点,连接“直径”的中点和1号点就行了)

而本来depth就小于ans的值根本就不用管。

因为题目要的是所有的x

所以我们考虑离线一下

di表示最大深度恰为i的点的最大距离

这个东西跑一遍dfs,在lca处记录统计一下即可

然后再后缀取个max,找到深度大于i时,点的最大距离

然后发现答案一定是单调递增的,于是我们枚举x的同时把答案算一下即可。

复制代码
#include <bits/stdc++.h>
using namespace std;
int las[600005],d[600005],N,Arrive[600005],cnt,nex[600005];
void jt(int x,int y){
    cnt++;
    nex[cnt]=las[x];
    las[x]=cnt;
    Arrive[cnt]=y;
}
void Clear(){
    cnt=0;
    for (int i=0;i<=N;i++)
        d[i]=0;
    for (int i=1;i<=N;i++)
        las[i]=0;
}
int dfs(int Now,int fa,int dep){
    int x1=dep,x2=dep;
    for (int i=las[Now];i;i=nex[i]){
        int v=Arrive[i];
        if (v==fa) continue;
        int nw=dfs(Arrive[i],Now,dep+1);
        if (nw>x1){
            x2=x1,x1=nw;
        }
        else if (nw>x2){
            x2=nw;
        }
    }
    int Dis=x2-1;
    if (Dis>=0)
        d[Dis]=max(d[Dis],x1+x2-2*dep+1);
    return x1;
}
void Init(){
    for (int i=1;i<=N-1;i++){
        int u,v;
        cin>>u>>v;
        jt(u,v);
        jt(v,u); 
    }
}
void Solve(){
    int Ans=dfs(1,1,0);
    for (int i=N-2;i>=0;i--) d[i]=max(d[i],d[i+1]);
    int ans=0;
    for (int i=1;i<=N;i++){
        while (ans<Ans&&i+d[ans]/2>ans) ans++;
        cout<<ans<<" ";
    }
    cout<<endl;
    return;
}
int main(){
    int T;
    cin>>T;
    while (T--){
        Clear();
        cin>>N;
        Init();
        Solve();
    }
}
复制代码

 CF1637D

稍微展开一下式子

考虑一个位置j的贡献

i<j(ai+aj)2

拆开

i<ja2i+(j1)aj2+i<j2aiaj

对于最后一项,把aj提出去

i<ja2i+(j1)aj2+2aji<jai

同样的,对于bj来说,把所有的a替换成b即可

发现交换两个数字时,会对答案产生影响的只有最后一项aji<jai

发现n<=100ai<=100

我们可以设计一个dp,dp[i][j]表示填到第i位,a前缀和为j时的答案

b的前缀和用 总的前缀和-a的前缀和即可转移

复制代码
#include <bits/stdc++.h>
using namespace std;
long long dp[105][105*105];
int a[105],b[105],Sum,N;
long long ans;
int T;
int main(){
    cin>>T;
    while (T--){
    scanf("%d",&N);
    ans=0,Sum=0;
    for (int i=1;i<=N;i++){
        scanf("%d",&a[i]);
    }
    for (int i=1;i<=N;i++){
        scanf("%d",&b[i]);
    }
    for (int i=1;i<=N;i++)
        for (int j=0;j<=100*N;j++)
            dp[i][j]=1e9+7; 
    dp[1][a[1]]=dp[1][b[1]]=0;
    long long Sum1=0;
    for (int i=1;i<=N;i++){
        Sum+=a[i]+b[i];
        for (int j=0;j<=100*N;j++){
            if (Sum-j-b[i]>=0&&j-a[i]>=0) dp[i][j]=min(dp[i][j],(i-1)*b[i]*b[i]+(i-1)*a[i]*a[i]+Sum1+dp[i-1][j-a[i]]+2ll*(j-a[i])*1ll*a[i]+2ll*(Sum-j-b[i])*1ll*b[i]);
            if (j-b[i]>=0&&Sum-j-a[i]>=0) dp[i][j]=min(dp[i][j],(i-1)*b[i]*b[i]+(i-1)*a[i]*a[i]+Sum1+dp[i-1][j-b[i]]+2ll*(j-b[i])*1ll*b[i]+2ll*(Sum-j-a[i])*1ll*a[i]);
        }
        Sum1+=a[i]*a[i]+b[i]*b[i];
    }
    long long Ans=1e9+7;
    for (int i=1;i<=100*N;i++)
        Ans=min(Ans,dp[N][i]);
    cout<<ans+Ans<<endl;;
    }
    return 0;
}
复制代码

 

2019-2020 ICPC Asia Hong Kong Regional Contest

队友好猛


随便写点东西(

 

D. Defining Labels

签到,显然是个进制转换再找找规律

随便写写就行了

 

C. Constructing Ranches

 

看到所有路径显然的想到点分治

形成多边形的条件是2*边中最大的大于其他所有的和

移项,存一下最大值和路径和,离散化一下树状数组统计答案

队友写的所以没有代码

 

J. Junior Mathematician

我写的.jpg(

显然的数位dp

容易想到用dp[i][j][k][l]表示填到i位,位数和在模m意义下为j,当前数在模m意义下为kf(x)在模m的意义下为l

但是发现这样5000*60*60*60内存爆了

发现后两维其实是为了判断f(x)x之间在模m意义下的关系而存在的

于是把后两维压成一维,即f(x)x

然后就够了

然后记得l1,或者像我的代码里直接判断l是否合法

然后我就被卡常了

教训是在多次调用的部分里取模少写,然后乘除法尽量少写

多写一个 *1ll 被卡爆了(

复制代码
#include <bits/stdc++.h>
using namespace std;
char ss1[5005],ss2[5005];
int M;
int mxx;
const int fish=1e9+7;
long long dp[5005][65][65];
int a[5005];
int Pow[5005];
int T;
long long dfs(int Now,int Sum,int F,bool chk){
    if (!Now) return (F==0);
    if (dp[Now][Sum][F]!=-1&&!chk) return dp[Now][Sum][F];
    int mx=chk?a[Now]:9;
    long long ans=0;
    for (int i=0;i<=mx;i++)
        ans+=dfs(Now-1,(Sum+i)%M,(F+Sum*i%M-Pow[Now-1]*i%M+M)%M,chk&&i==mx);
    (ans+=fish)%=fish;
    if (!chk) dp[Now][Sum][F]=ans;
    return ans;
}
void Clear(){
    for (int i=1;i<=mxx;i++)
        for (int j=0;j<=M;j++)
            for (int k=0;k<=M;k++)
                dp[i][j][k]=-1;
    return;
}
void Pre(){
    Clear();
    Pow[0]=1;
    for (int i=1;i<=mxx;i++){
        Pow[i]=Pow[i-1]*10ll%M;
    }
    return;
}
int main(){
    cin>>T;
    Pre();
    while (T--){
        scanf("%s%s%d",ss1,ss2,&M);
        int Len=strlen(ss1);
        mxx=Len;
        int Len1=strlen(ss2);
        mxx=max(mxx,Len1);
        Pre();
        for (int i=Len-1;i>=0;i--) a[Len-i]=ss1[i]-'0';
        a[1]--;
        for (int i=1;i<=Len;i++){
            if (a[i]<0) a[i+1]--,a[i]+=10;
            else break;
        }
        int ans=dfs(Len,0,0,1);
        Len=strlen(ss2);
        for (int i=Len-1;i>=0;i--)
            a[Len-i]=ss2[i]-'0';
        ans=(dfs(Len,0,0,1)-ans+fish)%fish;
        printf("%d\n",ans);
    }
    return 0;
}
复制代码

赛后补题:

I. Incoming Asteroids

最后半小时口胡了个思路,没写完(

发现K只有3,然后发现每次都要求输出所有满足条件的人,我的想法是用某种手段快速的找出可能是答案的人选,然后再一个个检定过去

发现x+y+z>m,则一定要有一个数字比m3

于是我们可以在每次出现当前位置的总时间-该元素进入时间 大于m3的时候才进行检定(即当前元素是所有需要的位置里最大的那个)

做完检定后,如果满足x+y+z>m的话,就把它加入答案队列里

如果不满足的话,则把剩下的部分重新按照相同的方式进行分配,扔回各自对应的set

这么做的话,发现其实每个元素每次至少会除2,所以其实每个元素会被访问到的次数是log级别的

然后用 set 维护一下就行。

复制代码
#include<bits/stdc++.h>
using namespace std;
struct Node{
    long long nd;
    int id;
    long long Time;
    bool operator <(const Node &a) const{
        return nd<a.nd||nd==a.nd&&id<a.id||nd==a.nd&&id==a.id&&Time<a.Time;
    }
};
set<Node> a[500005];
int b[500005][4],cnt,Nd[500005];
long long d[500005];
int lst;
Node c[500005][4];
int N,T;
int main(){
    scanf("%d%d",&N,&T);
    while (T--){
        int opt;
        scanf("%d",&opt);
        if (opt==1){
            int y,k;
            scanf("%d%d",&y,&k);
            y^=lst;
            Nd[++cnt]=y;
            for (int i=1;i<=k;i++){
                scanf("%d",&b[cnt][i]);
                b[cnt][i]=b[cnt][i]^lst;
                Node New;
                New.nd=(y+k-1)/k+d[b[cnt][i]];
                New.id=cnt;
                New.Time=d[b[cnt][i]];
                a[b[cnt][i]].insert(New);
                c[cnt][i]=New;
            }
        }
        else{
            int x,y;
            scanf("%d%d",&x,&y);
            x^=lst;y^=lst;
            d[x]+=y;
            vector<int> ans;
            while (!a[x].empty()){
                auto it=a[x].begin();
                Node Now=*it;
                if (d[x]>=Now.nd){
                    vector <Node> nw;
                    int xx=Now.id;
                    long long Get=0;
                    int K=0;
                    for (int i=1;i<=3;i++)
                        if (b[xx][i]){
                            K++;
                            Node Need;
                            Need=c[xx][i];
                            int yy=b[xx][i];
                            auto it1=a[yy].lower_bound(Need);
                            a[yy].erase(it1);
                            Get+=d[yy]-Need.Time;
                        }
                    Nd[xx]-=Get;
                    if (Nd[xx]<=0){
                        ans.push_back(xx);
                        continue;
                    }
                    for (int i=1;i<=3;i++)
                        if (b[xx][i]){
                            Node New;
                            New.id=xx;
                            New.nd=(Nd[xx]+(K-1))/K+d[b[xx][i]];
                            New.Time=d[b[xx][i]];
                            a[b[xx][i]].insert(New);
                            c[xx][i]=New;
                    }
                }
                else break;
            }
            printf("%d",ans.size());
            lst=ans.size();
            sort(ans.begin(),ans.end());
            for (int i=0;i<ans.size();i++){
                printf(" %d",ans[i]);
            }
            printf("\n");
        }
    }
    return 0;
}
复制代码

 The 2020 ICPC Asia Macau Regional Contest

A. Accelerator

期望题

首先题目这个式子你稍微处理一下会发现其实本质上就是求一个随机排序的数组的后缀积的和的期望

整体统计答案不好统计,可以转化成各个元素的贡献去推

大概是K!(NK)!/N!af1af2....afk

然后回发现前面的系数可以统一乘,后半段其实本质是从n个数的数组里选择k个数字乘起来然后求和的总贡献。

扔给负责多项式的学长之后说是直接ntt就行了

然后就过了

队友写的所以没有代码

D.Artifacts


模拟,没啥好说的

写的学长写了好久,最后我上机帮忙debug了两下过了(

复制代码
#include <bits/stdc++.h>
using namespace std;
double ans;
double ATK,ATKRate,CritDMGRate,CritRate,a;
string s;
int main()
{
    ATK=ATKRate=CritDMGRate=CritRate=a=0.00;
    ATK=0.00;CritRate=0.05;CritDMGRate=0.50;
    for(int i=1;i<=5;i++)
    {
//        ATK=ATKRate=CritDMGRate=CritRate=a=0.00;
//        s="";
//        ATK=0.00;CritRate=0.05;CritDMGRate=0.50;
        int flag=0;
        double dd=1.00;
        bool book=false;
        for(int j=1;j<=5;j++)
        {
            s="";a=0.00;flag=0;book=false;dd=1.00;
            char c;
            while(c=getchar())
            {
                if(c=='+')
                {
                    if(s=="ATK") flag=1;
                    else if(s=="ATK Rate") flag=2;
                    else if(s=="Crit DMG Rate") flag=3;
                    else if(s=="Crit Rate") flag=4;
                    break;
                }
                if (c!='\n')s=s+c;
            }
            while((c=getchar())&&c!='\n')
            {
                if(c=='%') break;
                if(c=='.')
                {
                    book=true;
                    continue;
                }
                a=a*10.00+(c-'0')*1.00;
                if(book) dd*=10.00;
            }
            a=a/dd;
            if(flag==1)
            {
                //cout<<a<<endl; 
                ATK+=a;
            }
            else if(flag==2)
            {
                //scanf("%lf",&a);
                a=a/100.00;
                ATKRate+=a;
                //cout<<a<<endl;
            }
            else if(flag==3)
            {
                //scanf("%lf%",&a);
                a=a/100.00;
                CritDMGRate+=a;
                //cout<<a<<endl;
            }
            else if(flag==4)
            {
                a=a/100.00;
                CritRate+=a;
                //cout<<a<<endl;
            }
        }
    }
 
    ATK=ATK+1500.00*(1.00+ATKRate);
    CritRate=min(CritRate,1.00);
    ans=ATK*(1+CritDMGRate*CritRate);
    printf("%.10lf",ans);
    return 0;
}
复制代码

G. Game on Sequence

我写的(

因为写的太丑贡献了6发罚时

我是傻逼(


首先可以比较容易的想到一个dp
dpi表示以i为开头,先手必胜或必败

那么显然转移就是

如果i可以到的一个点是必败态,那么这个点就是必胜态

但是如果大力跑这东西每次都是O(n)的,会T飞

发现题目有个小提示,ai的范围最多只到255,复杂度应该会有一个ai

所以我开始思考重复元素的性质

发现其实重复元素除了最后一个元素,其他的都是必胜的

prove:

对于最后一个元素来说,如果它是先手必胜态的话,那么一定在它之后有一个先手必败态让它成为了必胜态。

而因为元素相同,所以我可以越过这个元素直接跳到那个必败的位置,先手胜利

如果最后一个元素是必败的话,那直接跳到这个元素即可。

于是我们就发现其实这个dp只会和最多255个位置有关系,每次跑询问的时候转移一下就行。

然后有一个小trick,添加数字的次数比询问次数多,所以操作的时候不算答案,之后询问的时候跑这个dp就行了,转移的时候显然优先转移靠后的元素。

复制代码
#include <bits/stdc++.h>
using namespace std;
int N,M;
int a[400005],cnt,lst[500];
bool dp[500];
void Add(){
    priority_queue<int> b;
    for (int i=0;i<=255;i++)
    if (lst[i]) b.push(lst[i]);
    while (!b.empty()){
        int nw=b.top();
        b.pop();
        bool flag=false;
        for (int i=0;i<8;i++){
            int y=a[nw]^(1<<i);
            if (dp[y]==0&&lst[y]>nw){
             flag=true;break;
            }
        }
        if (!flag) dp[a[nw]]=0;
        else dp[a[nw]]=1;
    }
}
int main(){
    scanf("%d%d",&N,&M);
    for (int i=1;i<=N;i++){
        int xx;
        scanf("%d",&xx);
        cnt++;
        a[cnt]=xx;
        lst[xx]=cnt;
    }
    for (int i=1;i<=M;i++){
        int opt;
        scanf("%d",&opt);
        if (opt==1){
            int xx;
            scanf("%d",&xx);
            cnt++;
            a[cnt]=xx;
            lst[xx]=cnt;
        }
        else{
            int pos;
            scanf("%d",&pos);
            Add();
            if (pos!=lst[a[pos]]) printf("Grammy\n");
            else if (dp[a[pos]]==1) printf("Grammy\n");
            else printf("Alice\n");
        }
    }
    return 0;
}
复制代码

L. Random Permutation

结论

类似于错排的推法,f(x)表示排列长度为x的时候的期望答案

然后把x这个数塞进排列就好了

所以答案是

f(x)=f(x1)x(nx+1)/n

暴力跑这个函数的值即可

代码不贴了

赛后补题:

 F. Fixing Networks

我是弱智

首先,比较显然的想法是先构造d+1个点的完全图,把c1个联通图先弄好

现在问题就转化成了如下问题:

构造一个有m个点的无向图,每个点的度都是k,是否有解

有则输出方案,没有则输出no

由于这个图的对称性非常强,所有点的度都一样

其实不难想到(但是赛时没想到……)可以把所有的点做成一个环

因为这样的话每个点覆盖别人和被覆盖的次数都会是一样的

然后每个点向环上前k2和后k2个点连边(下取整)

然后因为对称性,如果d是奇数的话一定会少一条,而和它对面的那个点也少一条,所以两个接上就好了

判断无解的条件就是 md不能同时是奇数,否则无法完全配对。

没了(

复制代码
#include <bits/stdc++.h>
using namespace std;
int n,d,c;
int Getid(int x,int md){
    int t;
    if (x<0) t=((x+md)%md);
    else t=x%md;
    if (t!=0) return t;
    else return md;
} 
int main(){
    cin>>n>>d>>c;
    if (d==1){
        if(n==c*2){
        printf("Yes\n");
        for (int i=1;i<=n;i++)
            if (i&2) printf("%d\n",i+1);
            else printf("%d\n",i-1);
        }
        else printf("No");
        return 0;
    }
    if (d==0){
        if (n==c){
            printf("Yes\n");
        }
        else printf("No\n");
        return 0;
    }
    int nd=c*(d+1);
    if (nd>n){
        printf("No\n");
        return 0;
    }
    int res=n-(d+1)*(c-1);
    if ((res&1)&&(d&1)) printf("No\n");
    else{
        printf("Yes\n");
        int T=d/2;
        for (int i=1;i<=res;i++){
            set<int> ans;
            for (int j=i-T;j<=i+T;j++) if (j!=i) ans.insert(Getid(j,res));
            if (d&1) ans.insert(Getid(i+res/2,res));
            for (auto j:ans) printf("%d ",j);
            printf("\n");
        }
        for (int i=res+1;i<=n;i+=(d+1)){
            for (int j=i;j<=i+d;j++){
                for (int k=i;k<=i+d;k++){
                    if (j==k) continue;
                    else printf("%d ",k);
                }
                printf("\n");
            }
        }
    }
    return 0;
}
复制代码

 C. Club Assignment

最小异或生成树

把数字一个个从高位到低位插入最小异或生成树中

首先,三个以上相同的数答案一定为0,特判。

然后考虑什么子树有贡献

当子树大小为1,2,3,4时才有贡献

对于2,直接把两个数字分开放

3的话,一定一个子树是1,一个子树是2

子树是2的肯定优先放在两个集合,保证高位大小

所以枚举剩下那个位置和谁配对

4是2+2,也是暴力枚举谁和谁配对

对于更大的子树,一定可以通过递归到<=4的子树

复制代码
#include<bits/stdc++.h>
using namespace std;
int N,cnt;
int L[31*200005],R[32*200005],Tree[31*200005][3],as[200005];
long long Ans;
struct Node{
    int x;
    long long id;
}a[200005];
int temp(Node x,Node y){
    return x.x<y.x;
}
void Insert(long long x,int pos){
    int Now=0;
    for (int i=32;i>=0;i--){
        if ((x>>i)&1ll){
            int &nw=Tree[Now][1];
            if (!nw) nw=++cnt;
            if (!L[nw]) L[nw]=pos;
            R[nw]=pos;
            Now=nw;
        }
        else{
            int &nw=Tree[Now][0];
            if (!nw) nw=++cnt;
            if (!L[nw]) L[nw]=pos;
            R[nw]=pos;
            Now=nw;
        }
    }
}
void Getans(int rt,int pos){
    int x=Tree[rt][0],y=Tree[rt][1];
    if (y)  Getans(y,pos-1);
    if (x)  Getans(x,pos-1);
    if (!rt) return;
        int xx=R[x]-L[x]+1,yy=R[y]-L[y]+1;
        if (R[rt]-L[rt]+1<=2){
            as[a[L[rt]].id]=1;
            as[a[R[rt]].id]=2;
            return;
        }
        if (R[rt]-L[rt]+1==3){
            if (!x||!y) return;
            int xx=R[x]-L[x]+1,yy=R[y]-L[y]+1;
            if (xx==1){
                long long ans=0;
                int ps=0;
                for (int i=L[y];i<=R[y];i++)
                    if (ans<(a[L[x]].x^a[i].x)){
                        ans=a[L[x]].x^a[i].x;
                        ps=i;
                }
                //cout<<ps<<endl;
                Ans=min(Ans,ans);
                as[a[L[x]].id]=as[a[ps].id];
            }
            else{
                long long ans=0;
                int ps=0;
                for (int i=L[x];i<=R[x];i++)
                    if (ans<(a[L[y]].x^a[i].x)){
                        ans=a[L[y]].x^a[i].x;
                        ps=i;
                    }
                Ans=min(Ans,ans);
                as[a[L[y]].id]=as[a[ps].id];
                //cout<<a[L[y]].id<<" "<<a[ps].id<<endl;
            }
            return;
        }
        else
        if (R[rt]-L[rt]+1==4){
            if (!x||!y) return;
            int xx=R[x]-L[x]+1,yy=R[y]-L[y]+1;
            if (xx==2&&yy==2){
                long long a1=min(a[R[x]].x^a[R[y]].x,a[L[x]].x^a[L[y]].x);
                long long a2=min(a[R[x]].x^a[L[y]].x,a[L[x]].x^a[R[y]].x);
                if (a1>=a2){
                    Ans=min(Ans,a1);
                    //cout<<"qwq"<<endl;
                    as[a[L[x]].id]=as[a[L[y]].id];
                    as[a[R[x]].id]=as[a[R[y]].id];
                }
                else{    
                    //cout<<Ans<<endl;
                    //cout<<"qwq"<<endl;
                    Ans=min(Ans,a2);
                    //cout<<Ans<<endl;
                    as[a[R[x]].id]=as[a[L[y]].id];
                    as[a[L[x]].id]=as[a[R[y]].id];
                }
                return;
            }
        }
    return;
}
void Clear(){
    for (int i=0;i<=cnt;i++){
        Tree[i][0]=Tree[i][1]=L[i]=R[i]=0;
    }
    cnt=0;
    return;
}
int main(){
    int T;
    scanf("%d",&T);
    while (T--){
    Clear();
    Ans=1e17;
    map<int,int> mp;
    scanf("%d",&N);
    bool flag=false;
    for (int i=1;i<=N;i++){
        scanf("%d",&a[i].x);
        a[i].id=i;
        mp[a[i].x]++;
        as[i]=0;
        if (mp[a[i].x]>2) flag=true;
    }
    if (flag){
        printf("0\n");
        printf("1");
        for (int i=2;i<=N;i++)
            printf("2");
        printf("\n");
        continue;
    }
    sort(a+1,a+N+1,temp);
    for (int i=1;i<=N;i++) Insert(a[i].x,i);
    Getans(0,32);
    for (int i=1;i<=N;i++)
        if (!as[i]) as[i]=1;
    bool flag1=false,flag2=false;
    for (int i=1;i<=N;i++){
        if (as[i]==1) flag1=true;
        else flag2=true;
    }
    if (!flag1) as[1]=1;
    if (!flag2) as[1]=2;
    printf("%lld\n",Ans);
    for (int i=1;i<=N;i++)
        printf("%d",as[i]);
    printf("\n");
    }
    return 0;
}
复制代码

2020-2021 ACM-ICPC, Asia Nanjing Regional Contest

E. Evil Coordinate

考虑分类讨论

首先终点如果是个地雷输出impossible

假设地雷点在(0,0)的右上方

于是我们可以考虑先尽量往远离它的方向走,先左后右,先下后上

但是这样会遇到一个问题

如果遇到地雷点有一维坐标恰好和终点相同时,这样模拟可能会导致在最后一次走的过程中撞到地雷

于是我们可以通过调整向上/下,向左/右的顺序来规避这个情况

发现其实范围并不太大

为了避免复杂的分类讨论,直接暴力枚举全排列来做一个模拟。

队友写的所以没有代码

F. Firework

打上花火

首先,最优策略肯定是每做k个放一次

于是我们不妨假设这个最优的点是k

第一次放有一个perfect的时间期望:(nk+m)[1(1p)k]

第二次放有一个perfect的时间期望:2(nk+m)[(1(1p)k)(1p)k

(前k次全部没有,后k次至少一个)

第三次放有一个perfect的时间期望:3(nk+m)[(1(1p)k)(1p)2k

以此类推

然后根据期望的线性性求个和

nk+m1(1p)k也提出来

然后会发现,求和的项构成一个以(1p)k的为公比的等比数列和一个以1为公差的等差数列相乘的结果

运用高中数学求一下这个求和式子趋于无穷时的解

最后解出来是kn+m1(1p)n

对着这个式子求一下二阶导,发现是单峰的

三分一下即可

复制代码
#include <bits/stdc++.h>
using namespace std;
int T,N,M,P;
double p;
const double eps=1e-6;
double F(double k){
    double ans = (N*k+M)/(1-pow(p,k));
    return ans;
}
int main(){
    scanf("%d",&T);
    while (T--){
        scanf("%d%d%d",&N,&M,&P);
        if (P==10000){
            printf("%d\n",N+M);
            continue; 
        }
        p=1-P/10000.0;
        double l=0,r=1e9;
        while (fabs(l-r)>1e-7){
            double mid=(l+r)/2;
            //cout<<F(mid-eps)<<endl;
            if (F(mid-eps)>F(mid+eps)) l=mid;
            else r=mid;
        }
        int ans=l;
        printf("%.10lf\n",min(F(ans),F(ans+1)));
    } 
    return 0;
} 
复制代码

H. Harmonious Rectangle

考虑一个2n的矩阵

显然,当n>9的时候,无论怎么填都是满足题意的

所以当n>9或者m>9的时候,答案就是3mn

n<=9m<=9的时候直接dfs打表即可

复制代码
#include <bits/stdc++.h>
using namespace std;
int N,T,M;
const long long fish=1e9+7;
const int Table[][10]={
{0, 0, 0, 0, 0, 0, 0, 0, 0,}, 
{0, 15, 339, 4761, 52929, 517761, 4767849, 43046721, 387420489,},
{0, 339, 16485, 518265, 14321907, 387406809, 460338013, 429534507, 597431612,},
{0, 4761, 518265, 43022385, 486780060, 429534507, 792294829, 175880701, 246336683,}, 
{0, 52929, 14321907, 486780060, 288599194, 130653412, 748778899, 953271190, 644897553,}, 
{0, 517761, 387406809, 429534507, 130653412, 246336683, 579440654, 412233812, 518446848,},
{0, 4767849, 460338013, 792294829, 748778899, 579440654, 236701429, 666021604, 589237756,},
{0, 43046721, 429534507, 175880701, 953271190, 412233812, 666021604, 767713261, 966670169,}, 
{0, 387420489, 597431612, 246336683, 644897553, 518446848, 589237756, 966670169, 968803245},
};
long long Pow(long long x,long long y){
    long long ans=1;
    while (y){
        if (y&1) ans=1ll*ans*x%fish;
        x=1ll*x*x%fish;
        y>>=1ll;
    }
    return ans%fish;
}
int main(){
    scanf("%d",&T);
    int N,M;
    while (T--){
        scanf("%d%d",&N,&M);
        if (N==1||M==1) cout<<"0\n";else
        if (N>9||M>9) printf("%lld\n",Pow(3ll,1ll*N*M));
        else printf("%d\n",Table[N-1][M-1]);
    }
    return 0;
}
复制代码

K. K Co-prime Permutation

构造满足(i,pi)=1的对数为k的排列

(i,i+1)=1

所以显然把前k个数循环右移一位即可。

注意特判k=0

代码是学长写的,不贴了

L. Let's Play Curling

转化成数轴上的点的距离

画画图发现其实本质上就是两个b之间最多的a的数量

M. Monster Hunter

树上背包,扔给学长写的,回头可能会自己写一遍

赛后补题:

D. Degree of Spanning Tree

考虑先随便找一颗生成树

一个显然的结论,一颗生成树里最多一个度大于n2的点

考虑怎么调整这个度大于n2的点

先把它放到根上

如果两个点的lca是这个点,那么把这两个点接上之后,可以删掉一个根的度(画图理解)

但是有一种情况,如果删完之后又产生了一个大于n2的点

就说明接的这个点实际上是接着根的,分类讨论一下换一个点接上就好了

动态查询lca可以写个lct,但是对于这题来说,我们只需要知道他们的lca是不是根。所以对直接接在根上的点做并查集,如果同属于一个集合就不是。

不断调整直到无法调整为止

写的时候注意细节和清空数组

复制代码
#include<bits/stdc++.h>
using namespace std;
int N,M;
vector<pair<int,int> >a;
map<pair<int,int> ,int> mp;
int fa[100005],Fa[100005];
int las[400005],cnt,nex[400005],d[100005],Arrive[400005];
void jt(int x,int y){
    cnt++;
    nex[cnt]=las[x];
    las[x]=cnt;
    Arrive[cnt]=y;
}
int Getfa(int x){return (x==fa[x])?x:fa[x]=Getfa(fa[x]);}
int Getfa1(int x){return (x==Fa[x])?x:Fa[x]=Getfa1(Fa[x]);}
void dfs(int Now,int faa,int Rt){
    fa[Now]=Rt;
    for (int i=las[Now];i;i=nex[i]){
        int v=Arrive[i];
        if (v==faa) continue;
        dfs(v,Now,Rt);
    }
}
void Clear(){
    a.clear();
    mp.clear();
    cnt=0;
    for (int i=1;i<=N;i++) d[i]=las[i]=0;
}
int main(){
    int T;
    scanf("%d",&T);
    while (T--){
        Clear();
        scanf("%d%d",&N,&M);
        for (int i=1;i<=M;i++){
            int u,v;
            scanf("%d%d",&u,&v);
            if (u>v) swap(u,v);
            pair<int,int> nw=make_pair(u,v);
            if (mp[nw]||u==v) continue;
            mp[nw]=true;
            a.push_back(nw);
        }
        for (int i=1;i<=N;i++)
            Fa[i]=i;
        map<pair<int,int>,bool> Cho;
        for (auto nw:a){
            int u=nw.first,v=nw.second;
            int fu=Getfa1(u),fv=Getfa1(v);
            if (fu!=fv){
                Fa[fu]=fv;
                jt(u,v);
                jt(v,u);
                d[u]++,d[v]++;
                Cho[nw]=true;
            }
        }
        bool flag1=false;
        for (int i=2;i<=N;i++)
            if (Getfa1(i)!=Getfa1(1)) {flag1=true;break;}
        if (flag1){
            printf("No\n");
            continue;
        }
        bool flag=false;
        int rt=0;
        for (int i=1;i<=N;i++)
            if (d[i]>(N/2)){
                flag=true;
                rt=i;
                break; 
        }
        if (!flag){
            printf("Yes\n");
            for(auto nw:a){
                if (Cho[nw])
                    printf("%d %d\n",nw.first,nw.second);
            }
            continue;
        }
        for (int i=1;i<=N;i++) fa[i]=i;
        for (int i=las[rt];i;i=nex[i])
            dfs(Arrive[i],rt,Arrive[i]);
        for (auto nw:a){
            if (Cho[nw]) continue;
            int u=nw.first,v=nw.second;
            int fx=Getfa(u),fy=Getfa(v);
            if (fx==fy||u==rt||v==rt) continue;
            d[rt]--;d[fx]--;
            d[u]++;d[v]++;
            if (d[u]>N/2||d[v]>N/2){
                d[fx]++,d[fy]--;
                if (d[u]>N/2||d[v]>N/2){
                    d[rt]++;
                    d[fy]++;
                    d[u]--;
                    d[v]--;
                    continue;
                }
                else{
                    fa[fy]=fx;
                    Cho[nw]=true;
                    Cho[{min(rt,fy),max(rt,fy)}]=false;
                }
            }
            else{
                fa[fx]=fy;
                Cho[nw]=true;
                Cho[{min(rt,fx),max(rt,fx)}]=0;
            }
            if (d[rt]<=N/2) break;
        }
        if (d[rt]<=N/2){
            printf("Yes\n");
            for (auto nw:a){
                if (Cho[nw]) printf("%d %d\n",nw.first,nw.second);
            }
        }
        else printf("No\n");
    }
    return 0;
}
复制代码

 

J. Just Another Game of Stones

两个操作分开处理

区间取min/max操作是个经典的吉老师线段树

考虑怎么回答询问

我们知道NIM游戏必胜的条件是区间xor和不为0

先手只要取一堆,导致后手的xor和为0就是必败

那么其实只要求区间的xors之后,统计

axors<=a的数的个数

显然,按照s最高位讨论就可以了

之后可能会有个吉老师线段树的学习笔记

复制代码
#include <bits/stdc++.h>
using namespace std;
const int M=2e5+10; 
struct Node{
    int mn,sec,num,tag;
    int Sum,cnt[33];
}Tree[4*M];
int a[M];
const int inf=1<<30;
void PushUp(int x){
    Tree[x].Sum=Tree[x<<1].Sum^Tree[x<<1|1].Sum;
    for (int i=0;i<30;i++)
        Tree[x].cnt[i]=Tree[x<<1].cnt[i]+Tree[x<<1|1].cnt[i];
    Node a=Tree[x<<1],b=Tree[x<<1|1];
    if (a.mn<b.mn){
        Tree[x].mn=a.mn;
        Tree[x].num=a.num;
        Tree[x].sec=min(a.sec,b.mn);
    }
    else if (a.mn>b.mn){
        Tree[x].mn=b.mn;
        Tree[x].num=b.num;
        Tree[x].sec=min(b.sec,a.mn);
    }
    else{
        Tree[x].mn=a.mn;
        Tree[x].num=a.num+b.num;
        Tree[x].sec=min(a.sec,b.sec);
    }
}
void PushDown(int x){
    int nw=Tree[x].tag;
    if (nw>Tree[x<<1].mn&&nw<Tree[x<<1].sec){
        for (int i=0;i<30;i++){
            int st=1<<i;
            if (st>nw&&st>Tree[x<<1].mn) break;
            if (Tree[x<<1].mn&st) Tree[x<<1].cnt[i]-=Tree[x<<1].num;
            if (nw&st) Tree[x<<1].cnt[i]+=Tree[x<<1].num;
        }
        if (Tree[x<<1].num&1) Tree[x<<1].Sum^=Tree[x<<1].mn^nw;
        Tree[x<<1].mn=nw;
        Tree[x<<1].tag=nw; 
    }
    if (nw>Tree[x<<1|1].mn&&nw<Tree[x<<1|1].sec){
        for (int i=0;i<30;i++){
            int st=1<<i;
            if (st>nw&&st>Tree[x<<1|1].mn) break;
            if (Tree[x<<1|1].mn&st) Tree[x<<1|1].cnt[i]-=Tree[x<<1|1].num;
            if (nw&st) Tree[x<<1|1].cnt[i]+=Tree[x<<1|1].num;
        }
        if (Tree[x<<1|1].num&1) Tree[x<<1|1].Sum^=Tree[x<<1|1].mn^nw;
        Tree[x<<1|1].mn=nw;
        Tree[x<<1|1].tag=nw; 
    }
}
void Build(int Now,int l,int r){
    if (l==r){
        Tree[Now].tag=Tree[Now].mn=Tree[Now].Sum=a[l];
        for (int i=0;i<30;i++){
            int st=1<<i;
            if (a[l]&st)
                Tree[Now].cnt[i]++;
            }
        Tree[Now].sec=inf,Tree[Now].num=1;
        return;
    }
    int mid=(l+r)>>1;
    Build(Now<<1,l,mid),Build(Now<<1|1,mid+1,r);
    PushUp(Now);
}
void Modify(int Now,int l,int r,int L,int R,int x){
    if (L<=l&&r<=R){
        if (x<=Tree[Now].mn) return;
        int nw=x;
        if (Tree[Now].mn<x&&x<Tree[Now].sec){
            for (int i=0;i<30;i++){
                int st=1<<i;
                if (st>x&&st>Tree[Now].mn) break;
                if (Tree[Now].mn&st) Tree[Now].cnt[i]-=Tree[Now].num;
                if (nw&st) Tree[Now].cnt[i]+=Tree[Now].num;
        }
        //
        if (Tree[Now].num&1) Tree[Now].Sum^=Tree[Now].mn^nw;
        Tree[Now].mn=nw;
        Tree[Now].tag=nw; 
        }
        else{        
            PushDown(Now);
            //if (Now==627376) cout<<x<<" "<<Tree[Now].mn<<endl;
            int mid=(l+r)>>1;
            Modify(Now<<1,l,mid,L,R,x),Modify(Now<<1|1,mid+1,r,L,R,x);
            PushUp(Now);
        }
        return;
    }
    PushDown(Now);
    int mid=(l+r)>>1; 
    if (L<=mid) Modify(Now<<1,l,mid,L,R,x);
    if (mid<R)  Modify(Now<<1|1,mid+1,r,L,R,x);
    PushUp(Now);
}
int GetAns(int Now,int l,int r,int L,int R,int w){
    if (L<=l&&r<=R) return Tree[Now].cnt[w];
    int mid=(l+r)>>1;
    if (l!=r) PushDown(Now);
    int ans=0;
    if (L<=mid) ans+=GetAns(Now<<1,l,mid,L,R,w);
    if (mid<R)  ans+=GetAns(Now<<1|1,mid+1,r,L,R,w);
    return ans;
}
int GetSum(int Now,int l,int r,int L,int R){
    if (L<=l&&r<=R) return Tree[Now].Sum;
    int mid=(l+r)>>1;
    if (l!=r) PushDown(Now);
    int ans=0;
    if (L<=mid) ans^=GetSum(Now<<1,l,mid,L,R);
    if (mid<R)  ans^=GetSum(Now<<1|1,mid+1,r,L,R);
    return ans;
}
int main(){
    int N,T;
    scanf("%d%d",&N,&T);
    for (int i=1;i<=N;i++) scanf("%d",&a[i]);
    Build(1,1,N);
    while (T--){
        int opt,l,r,x;
        scanf("%d%d%d%d",&opt,&l,&r,&x);
        if (opt==1) Modify(1,1,N,l,r,x);
        else{
            int st;
            st=GetSum(1,1,N,l,r);
            int pos=0;
            st^=x;
            if (!st) {
                printf("0\n");
                continue;
            }
            for (int i=29;i>=0;i--)
                if (st&(1<<i)) {
                    pos=i;
                    break;
                }
            printf("%d\n",GetAns(1,1,N,l,r,pos)+((x^st)<=x));
        }
    }
    return 0;
}
复制代码

 

2018-2019 ACM-ICPC, Asia Jiaozuo Regional Contest

A. Xu Xiake in Henan Province

签到,模拟

D. Keiichi Tsuchiya the Drift King

高中数学

分两种情况讨论

一种是车整个进入弯道,另一种情况是车没有完全进入弯道

前者最远点的轨迹显然是一个圆,算个半径完事

后者做一条平行线,算三角函数

临界条件是刚好出弯的时候碰到直道的角度

复制代码
#include <bits/stdc++.h>
using namespace std;
int a,b,r,d;
int T;
#define PI 3.141592653589793
#define eps 1e-6
int main(){
    cin>>T;
    while (T--){
        cin>>a>>b>>r>>d;
        double Lim=1.0*b/(r+a);
        double Ang=d/360.0*2*PI;
        double Ang1=atan(Lim);
        double ans=0;
        if (Ang-Ang1>eps) ans=sqrt((r+a)*(r+a)+b*b)-r;
        else ans=(r+a)/cos(Ang)+(b-(r+a)*tan(Ang))*sin(Ang)-r;
        printf("%.10lf\n",ans);
    }
    return 0;
}
复制代码

F. Honeycomb

BFS,注意读入格式

复制代码
#include <bits/stdc++.h>
using namespace std;
const int N=6e3+10,M=1e3+10;
struct node
{
    int x,y,d;
};
char g[N][N];
bool vis[N][N];
vector<int>dx{-1,-2,-1,1,2,1}; 
vector<int>dy{-3,0,3,3,0,-3};
vector<int>centerx{-2,-4,-2,2,4,2};
vector<int>centery{-6,0,6,6,0,-6};
int main()
{
    int T;
    cin>>T;
    while(T--)
    {
        int r,c;
        cin>>r>>c;
        getchar();
        for(int i=1;i<=4*r+3;i++) 
            gets(g[i]+1);
        pair<int,int>S,T;
        for(int i=1;i<=4*r+3;i++)
            for(int j=1;j<=6*c+3;j++)
            {
                vis[i][j]=0;
                if(g[i][j]=='S') S={i,j};
                if(g[i][j]=='T') T={i,j}; 
            }
        queue<node>q;
        q.push({S.first,S.second,0});
        vis[S.first][S.second]=1;
        bool flag=0;
        while(q.size())
        {
           node t=q.front();
           q.pop();
           if(t.x==T.first&&t.y==T.second)
           {
                cout<<t.d+1<<'\n';
                flag=true;
                break;
           }
           for(int i=0;i<6;i++)
           {
                int x=t.x+dx[i];
                int y=t.y+dy[i];
                if(g[x][y]==' '&&x>0&&y>0&&x<=4*r+3&&y<=6*c+3)
                {
                    int nx=t.x+centerx[i],ny=t.y+centery[i];
                    if(!vis[nx][ny]&&nx>0&&ny>0&&nx<=4*r+3&&ny<=6*c+3)
                    q.push({nx,ny,t.d+1}),vis[nx][ny]=1;
                }
           }
        }
        if(!flag) cout<<-1<<'\n';
    }
    return 0;
}
复制代码

B的贪心策略最后三十分钟想的差不多了,但是没机时了

C感觉应该是维护四根线,把动棋子转化成动线,然后按照线维护答案,也没来得及细想

感觉时间分配有问题

赛后补题

B. Ultraman vs. Aodzilla and Bodzilla

最后三十几分钟口胡了一下做法

把两个怪的贡献拆开算

k1atka+k2atkb

其中一定有一个是总的轮数,另一个是某个怪被击杀的轮数

于是思路就来了

保证总轮数最少的前提下,使得一个怪被击杀的轮数尽可能少,同时保证字典序尽可能小

我们比较显然的会想到先一直打A,再去打B

但这样并不能保证总轮数最少

于是我们考虑先鲨A还是B

最小的总轮数显然是可以算出来的

然后我们还可以得到A的最小死亡轮数

这时候考虑B,如果在剩下的回合里可以打死B的话,那么我们显然直接打

如果不能,就把前面一些打A的轮数替换成B

那么怎么替换呢? 显然是替换尽可能靠后的

于是我们就只替换一个位置,就是A最大溢出伤害的位置。

而把B放在前面,思考方式类似

但是要考虑字典序

如果能在相应的回合杀掉A的话,那么就把B的前几个位置换成A(在保证打死B的前提下)

如果不能,就从小往大凑,尽可能在靠前的位置打A

复制代码
#include <bits/stdc++.h>
using namespace std;
int T;
const int N=1000005;
long long Sum[1000010];
int hpa,hpb,atka,atkb;
int main(){
    cin>>T;
    for (int i=1;i<=N;i++) Sum[i]=Sum[i-1]+i; 
    char ss1[N],ss[N];
    while (T--){
        scanf("%d%d%d%d",&hpa,&hpb,&atka,&atkb);
        int pos1=lower_bound(Sum+1,Sum+N+1,hpa)-Sum;
        int pos2=lower_bound(Sum+1,Sum+N+1,hpb)-Sum;
        int pos3=lower_bound(Sum+1,Sum+N+1,hpa+hpb)-Sum;
        int x=Sum[pos1]-hpa;
        int Sumb=Sum[pos3]-Sum[pos1];
        long long ans1=0;
        ans1=1ll*pos1*1ll*atka+1ll*pos3*1ll*atkb;
        for (int i=1;i<=pos1;i++)
            ss[i]='A';
        for (int i=pos1+1;i<=pos3;i++)
            ss[i]='B';
        ss[0]=' ';
        ss1[0]=' ';
        ss[pos3+1]='\0';
        if (Sumb<hpb) ss[x]='B';
        int pos=lower_bound(Sum+1,Sum+N+1,Sum[pos2]-hpb)-Sum;
        if (Sum[pos]>Sum[pos2]-hpb) pos--;
        long long ans2=1ll*pos2*1ll*atkb+1ll*pos3*1ll*atka;
        for (int i=1;i<=pos2;i++)
                ss1[i]='B';
        ss1[pos3+1]='\0';
        for (int i=pos2+1;i<=pos3;i++)
            ss1[i]='A';
        if (Sum[pos]+Sum[pos3]-Sum[pos2]>=hpa){
            for (int i=1;i<=pos;i++)
                ss1[i]='A';
        }
        else{
            int nd=hpa-(Sum[pos3]-Sum[pos2]);
            for (int i=1;i<=pos2;i++){
                if ((nd-i)>i||nd==i){
                    nd-=i;
                    ss1[i]='A';
                }
            }
        }
        if (ans1>ans2){
            printf("%lld",ans2);
            printf("%s\n",ss1);
        }
        if (ans1<ans2){
            printf("%lld",ans1);
            printf("%s\n",ss);
        }
        if (ans1==ans2){
            printf("%lld",ans1);
            bool flag=false;
            for (int i=1;i<=pos3;i++)
                if (ss[i]>ss1[i]) {flag=true;break;}
                else if (ss[i]<ss1[i]){flag=false;break;}
            if (flag)
                printf("%s\n",ss1);
            else printf("%s\n",ss);
        }
    }
    return 0;
} 
复制代码

 C. Supreme Command

现场想到了把棋子的移动改成边界的移动,但是不太会写这个东西就润了。

赛后参考了一下网上的代码,发现思路是对的

麻了,我的代码能力太差了

因为棋子贴贴之后就不会再分开

所以不妨把棋子的移动转化成棋盘边界的移动,计算偏移量就可以表示本来点的坐标

而边界里面的点是没有被边界控制的

然后维护四条线就可以了

现在考虑询问在同一个点的点对数量

由于初始的棋盘每行每列只有一个棋子,所以其实可以发现只有四个角有可能有棋子贴贴

所以每次在推进边界的时候,顺手扫描一下四个角,组合数计算一下即可。

复制代码
#include <bits/stdc++.h>
using namespace std;
int N,M;
bool vis[300005];
int a[300005],b[300005];
long long LeftUp,RightUp,LeftDown,RightDown;
long long C(long long x){
    return x*(x-1)/2;
}
struct Node{
    int pos[300005],id[300005];
    int l,r,d;
    void Add(int x,int Pos){
        pos[Pos]=x;
        id[x]=Pos;
    }
    int GetPos(int x){
        if (x<l) return l+d;
        if (x>r) return r+d;
        return x+d;
    }
}Line,Col;
void Clear(){
    Line.l=1,Line.r=N;
    Col.l=1,Col.r=N;
    Line.d=Col.d=0;
}
void Calc(int x){
    if (vis[x]) return;
    if (Line.pos[x]<=Line.l && Col.pos[x]<=Col.l) {
        vis[x]=true;
        LeftUp++;
    }else
    if (Line.pos[x]>=Line.r && Col.pos[x]<=Col.l){
        vis[x]=true;
        RightUp++;
    }
    else
    if (Line.pos[x]<=Line.l && Col.pos[x]>=Col.r){
        vis[x]=true;
        LeftDown++;
    }
    else
    if (Line.pos[x]>=Line.r && Col.pos[x]>=Col.r){
        vis[x]=true;
        RightDown++;
    }
}
void Lshift(Node &a,int k){
    while (a.l<a.r&&a.l+a.d-k<1) Calc(a.id[++a.l]);
    if (a.l+a.d-k>=1) a.d-=k;
    else a.d=1-a.l;
}
void Rshift(Node &a,int k){
    while (a.l<a.r&&a.r+a.d+k>N) Calc(a.id[--a.r]);
    if (a.r+a.d+k<=N) a.d+=k;
    else a.d=N-a.r; 
}
long long GetAns(){
    if (Line.l==Line.r && Col.l==Col.r){
        long long x=LeftUp+RightUp+RightDown+LeftDown;
        return C(x);
    }
    else if (Line.l==Line.r){
        long long x=LeftUp+RightUp,y=LeftDown+RightDown;
        return C(x)+C(y);
    }
    else if (Col.l==Col.r){
        long long x=LeftUp+LeftDown,y=RightUp+RightDown;
        return C(x)+C(y);
    }
    else return C(LeftUp)+C(LeftDown)+C(RightUp)+C(RightDown);
}
int main(){
    //freopen("data.in","r",stdin);
    //freopen("test.out","w",stdout);
    int T;
    scanf("%d",&T);
    while (T--){
        scanf("%d%d",&N,&M);
        Clear();
        LeftUp=RightUp=LeftDown=RightDown=0;
        for (int i=1;i<=N;i++)
            vis[i]=0;
        for (int i=1;i<=N;i++){
            scanf("%d%d",&a[i],&b[i]);
            Line.Add(b[i],i);Col.Add(a[i],i);
        }
        Calc(Line.id[1]); Calc(Line.id[N]);
        Calc(Col.id[1]); Calc(Col.id[N]);
        for (int i=1;i<=M;i++){
            char opt[3];
            scanf("%s",opt);
            if (opt[0]=='!') printf("%lld\n",GetAns());
            else{
                int x;
                scanf("%d",&x);
                if (opt[0]=='L') Lshift(Line,x);else
                if (opt[0]=='R') Rshift(Line,x);else
                if (opt[0]=='U') Lshift(Col,x);else
                if (opt[0]=='D') Rshift(Col,x);else
                printf("%d %d\n",Col.GetPos(a[x]),Line.GetPos(b[x]));
            }
        }
    }
    return 0;
}
复制代码

 L. Connected Subgraphs

FJWC2019子图(

分五类讨论,然后容斥一下,考虑每个三/四元环被多算的次数

虽然,但是,为什么邻接表存图跑的比vector慢,我不行了

复制代码
#pragma GCC optimize(1)
#pragma GCC optimize(2)
#pragma GCC optimize(3,"Ofast","inline")
#include<bits/stdc++.h>
using namespace std;
int T;
int N,M;
vector <int> G[400005];
long long read(){
    long long ans=0;
    char last=' ',ch=getchar();//last?????????????????????????????
    while(ch<'0' || ch>'9')last=ch,ch=getchar();//???????????????????last?????ch????????
    while(ch>='0' && ch<='9')ans=ans*10+ch-'0',ch=getchar();//??????????????????????
    if(last=='-')ans=-ans;//???????
    return ans;
}
inline void write(int x){
    if (x < 0) x = ~x + 1, putchar('-');
    if (x > 9) write(x / 10);
    putchar(x % 10 + '0');
}
const long long fish=1e9+7;
int cnt,nex[400005],las[400005],Arrive[400005],d[400005]; 
void jt(int x,int y){
    cnt++;
    nex[cnt]=las[x];
    las[x]=cnt;
    Arrive[cnt]=y;
}
bool cmp(int x,int y){
    return (d[x]==d[y]?x>y:d[x]>d[y]);
}
int Pow(int x,int y){
    int as=1;
    while (y){
        if (y&1) as=1ll*as*1ll*x%fish;
        x=1ll*x*1ll*x%fish;
        y>>=1;
    }
    return as;
}
int inv3=333333336,inv4=250000002,inv2=500000004,inv24=41666667;
int u[200005],v[200005];
int vis[200005],R3[200005],cnt4[200005],cnt3,ans4;
void Get_3(){
    for (int u=1;u<=N;u++){
        for (auto v:G[u]) vis[v]++;
        for (auto v:G[u]) {
            for (auto w:G[v]){
                if (vis[w]){
                    ++R3[w],++R3[u],++R3[v];
                }
            }
        }
        for (auto v:G[u]) vis[v]=0;
    }
    for (int i=1;i<=N;i++)
        (cnt3+=R3[i])%=fish;
    cnt3=1ll*cnt3*1ll*inv3%fish;
    return;
}
void Get_4(){
    for (int u=1;u<=N;u++){
        for (auto v:G[u]){
            if (cmp(u,v)){
                for (auto w:G[v]){
                    if (cmp(u,w)){
                        cnt4[u]+=vis[w];
                        cnt4[v]+=vis[w];
                        cnt4[w]+=vis[w];
                        vis[w]++;
                    }
                }
            }
        }
        for (auto v:G[u]){
            if (cmp(u,v)){
                for (auto w:G[v]){
                    if (cmp(u,w)){
                        cnt4[v]+=--vis[w];
                    }
                }
            }
        }
    } 
    for (int i=1;i<=N;i++)
        (ans4+=cnt4[i])%=fish;
    ans4=1ll*ans4*1ll*inv4%fish;
    //cout<<ans4<<endl;
    return;
}
int Get(){//?????+?????
    int ans=0;
    for (int i=1;i<=N;i++){
        (ans+=1ll*R3[i]*1ll*(d[i]-2)%fish)%=fish;
    }
    return ans;
} 
void init(){
    N=read();M=read();
    int k;
    //k=read(); 
    for (int i=1;i<=M;i++){
        d[u[i]=read()]++,d[v[i]=read()]++;
    }
    for (int i=1;i<=M;i++)
        if (cmp(u[i],v[i])) G[u[i]].push_back(v[i]);
        else G[v[i]].push_back(u[i]);
    Get_3();
    for (int i=1;i<=M;i++)
        if (cmp(u[i],v[i])) G[v[i]].push_back(u[i]);
        else G[u[i]].push_back(v[i]);
    Get_4();
    long long ans=0;
    for (int i=1;i<=N;i++){
        int x=d[i];
        //cout<<d[i]<<endl;
        ans=(ans+1ll*x*1ll*(x-3)%fish*1ll*(x-1)%fish*1ll*(x-2)%fish*1ll*inv24%fish)%fish;    
        //if(i<=10) cout<<x<<" "<<ans<<endl;
    }//??? 
     for (int i=1;i<=M;i++){
         int x=d[u[i]],y=d[v[i]];
         //cout<<d[u[i]]<<" "<<d[v[i]]<<endl;
        ans=(ans+1ll*(x-2)*1ll*(x-1)%fish*1ll*inv2%fish*(y-1)+1ll*(y-2)*1ll*(y-1)%fish*inv2%fish*(x-1)%fish)%fish; 
     }//???    
     ans=(ans-3ll*Get()%fish+fish)%fish;
     for (int u=1;u<=N;u++){
         int t=0;
         for (auto v:G[u]){
             (ans+=1ll*t*(d[v]-1)%fish)%=fish;
             (t+=d[v]-1)%=fish;
         }
     }//??     
     ans=(ans-3ll*cnt3+fish)%fish;
     ans=(ans-3ll*ans4%fish+fish)%fish;     
     //cout<<ans4<<endl;
     write(ans);
     printf("\n");
     return;
}
void Clear(){
    cnt=0;
    for (int i=1;i<=N;i++){
    R3[i]=cnt4[i]=vis[i]=d[i]=0;
    G[i].clear();
    }
    ans4=0;cnt3=0;
}
int main(){
    T=read();
    while (T--){
        Clear();
        init();
    }
    return 0;
} 
复制代码

 The 2021 CCPC Weihai Onsite

A. Goodbye, Ziyin!

没啥好说的,考虑根据度讨论即可。

D. Period

kmp求所有可能的循环节长度

求完每次询问二分一下即可。

复制代码
#include<bits/stdc++.h>
using namespace std;
const int maxn=1000005;
int lt,lw,nex[maxn];
char t[maxn],w[maxn];
vector<int> ans;
int main(){
    scanf("%s",t);
    lt=strlen(t);
    for(int i=1;i<lt;i++){
        int j=nex[i-1];
        while(j>0&&t[i]!=t[j]) j=nex[j-1];
        if(t[i]==t[j]) j++;
        nex[i]=j; 
    }
    int nw=nex[lt-1];
    while (nw){
        ans.push_back(lt-nw);
        nw=nex[nw-1];
    }
    sort(ans.begin(),ans.end());
    int T;
    int y=(lt+1)/2;
    scanf("%d",&T);
    while (T--){
        int x;
        scanf("%d",&x);
        if (x<=y) x=lt-x+1;
        auto pos=lower_bound(ans.begin(),ans.end(),x)-ans.begin();
        if (pos==ans.size()) printf("%d\n",0);
        else printf("%d\n",ans.size()-pos);
    }
    return 0;
}
复制代码

F. Stone

因数,套路的考虑一下

发现取得时候,当我开始取奇数我就不能取偶数,但我取偶数则可以决策是否取奇数

现在考虑一个情况

如果一堆石头全是偶数,且当前只能取奇数了

那么显然,这个人必败(偶数-奇数=奇数,不可能一次取完)

于是就发现,此时先手的操作是把所有的堆都变成偶数

于是它任取一个小于最小奇数的奇数即可

对于开局就全是偶数的情况,这种情况下我们肯定尽量选择偶数

考虑一个偶数=2kb
此时,我们设k1所有k中最小的

剩下的数有两种可能

2kb,其中k为奇数

2k12k2b,显然2k2b是偶数

我们此时扔掉2k1考虑,就是有奇数有偶数的情况1了。

于是我们还是第一轮把所有的奇数变成偶数即可。

复制代码
#include<bits/stdc++.h>
using namespace std;
int N,mn=1e9+7,cnt=0,mn2=1e9+7,mn1=1e9+7;
bool flag;
int main(){
    scanf("%d",&N);
    for (int i=1;i<=N;i++){
        int x;
        scanf("%d",&x);
        if (x&1){
            flag=1;
            if (x<mn) mn=x;
        }
        else{
            cnt=0;
            while (x%2==0){
                cnt++;
                x=x/2;
            }
            if (cnt<mn2) mn2=cnt,mn1=x;
            else if (cnt==mn2) mn1=min(mn1,x);
        }
    }
    mn=(mn+1)/2;
    mn1=(mn1+1)/2;
    if (flag) cout<<mn;
    else cout<<mn1;
    return 0;
}
复制代码

 赛后补题:

I. Distance

下文涉及除法不特殊说明均为下取整考虑x=p1k1p2k2...pnkn

那么显然,x能走到y的话,一定是沿着质因子一直走

也就是其实我们只需要对质因子分开考虑

枚举每个质因子的贡献

其实是pi*(ij)*(cnt_i - cnt_j)

其中cnti表示pi指数为i的数字的数量

然后进一步考虑优化

考虑cnti怎么算

容斥一下,npi是至少ip,np(i+1)是至少i+1个p

两个对减一下就行了

对于大于(n)的数字来说,每个数字只会枚举到0次和1

也就是pcntpcnt1

展开一下,即p(nnp)(n)

整除分块一下,发现其实要维护的是素数的前缀和

就相当于min_25筛中,f(p)=p的函数前缀

维护一下即可

对于小于n的部分暴力即可

后面可能还有个min_25筛的学习笔记

复制代码
#include <bits/stdc++.h>
using namespace std;
long long N;
const long long fish=1e9+7;
long long mx;
const int xx=6e5+10;
long long g[xx],sp[xx],h[xx],Prime[xx],w[xx];
int m,id1[xx],id2[xx],Index;
bool IsPrime[xx];
void Pre(){
    IsPrime[1]=true;
    for (long long i=2;i<=mx;i++){
        if (!IsPrime[i]) Prime[++Index]=i,sp[Index]=(sp[Index-1]+i)%fish;
        for (int j=1;j<=Index&&1ll*i*Prime[j]<=mx;j++){
            IsPrime[1ll*i*Prime[j]]=true;
            if (i%Prime[j]==0) break;
        }
    }
}
long long Pow(long long x,int y){
    long long ans=1;
    while (y){
        if (y&1) ans=1ll*ans*x%fish;
        x=1ll*x*x%fish;
        y>>=1;
    }
    return ans;
}
long long GetAns(long long x,int y){
    if (x<=1||Prime[y]>x) return 0;
    int k=(x<=mx)?id1[x]:id2[N/x];
    long long ans=(g[k]-h[k]-sp[y-1]+y-1+fish)%fish;
    if (y==1) ans+=2;
    for (int i=y;i<=Index&&1ll*Prime[i]*Prime[i]<=x;i++){
        long long t1=Prime[i],t2=1ll*Prime[i]*Prime[i];
        for (long long e=1;t2<=x;e++,t1=t2,t2*=Prime[i]){
            ans=(ans+1ll*(Prime[i]^e)*GetAns(x/t1,i+1)%fish+(Prime[i]^(e+1ll))%fish)%fish;
        }
    }
    return ans;
}
int Get(long long x){
    if (x>mx) return id2[N/x];
    else return id1[x];
}
int main(){
    cin>>N;
    mx=sqrt(N);
    Pre();
    long long inv=Pow(2,fish-2);
    for (long long i=1,j;i<=N;i=j+1ll){
        j=N/(N/i);w[++m]=N/i;
        g[m]=1ll*w[m]%fish*1ll*((w[m]+1)%fish)%fish;
        g[m]=1ll*g[m]*inv%fish;
        g[m]=(g[m]-1ll+fish)%fish;
        if (w[m]<=mx) id1[w[m]]=m;
        else id2[j]=m;
    }
    for (int j=1;j<=Index;j++)
        for (int i=1;i<=m&&1ll*Prime[j]*Prime[j]<=w[i];i++){
            long long k=w[i]/Prime[j];
            if (k<=mx) k=id1[k];else k=id2[N/k];
            (g[i]-=1ll*Prime[j]*(g[k]-sp[j-1])%fish)%=fish;
    }
    long long ans=0,Pre=g[Get(mx)];
    for (long long i=mx;i;i--){
        int k=Get(N/i);
        ans=(ans+1ll*i*(N-i)%fish*((g[k]-Pre+fish)%fish)%fish)%fish;
        Pre=g[k];
    }
    for (int i=1;i<=Index;i++){
        int res=0;
        for (long long j=1,a=Prime[i];a<=N;j++,a*=Prime[i]){
            for (long long k=0,b=1;k<=j-1;k++,b*=Prime[i]){
                ans+=Prime[i]*(j-k)%fish*((N/b-N/b/Prime[i])%fish)%fish*((N/a-N/a/Prime[i])%fish)%fish;
            }
        }
    }
    cout<<ans*2%fish;
    return 0;
}
复制代码

 

E.Chase!

如果当前数值大于换的期望就选择不换

如果不大于就选择换

换的期望可以预处理

具体操作就是每次计算上面的东西,初始的期望值是每个数和别的数各自配对一次。

复制代码
#include <bits/stdc++.h>
using namespace std;
long long N,K,T;
long long Sum,sum[100005];
int a[100005],b[100005];
double dp[100005];
const double eps=1e-4;
int main(){
    cin>>N>>K>>T;
    for (int i=1;i<=N;i++){
        cin>>a[i];
        Sum+=a[i];
        b[i]=a[i];
    }
    sort(a+1,a+N+1);
    for (int i=N;i>=1;i--){
        sum[i]=sum[i+1]+a[i];
    }
    double ress=(double)2*(N-1)*Sum/(double(N*(N-1)));
    dp[0]=ress;
    for (int i=1;i<=K;i++){
        long long l=1,summ=0,Times=0;
        for (int r=N;r>=1;r--){
            while (a[l]+a[r]<=dp[i-1]) l++;
            if (l>N) break;
            if (l<=r){
                summ+=sum[l]+(N-l+1)*a[r]-2*a[r];
                Times+=N-l;
            }
            else{
                summ+=sum[l]+(N-l+1)*a[r];
                Times+=N-l+1;
            }
        }
        long long res=N*(N-1)-Times;        
        dp[i]=(double)summ/(N*(N-1))+(double)res*dp[i-1]/(double)(N*(N-1));         
    }
    printf("%.10lf\n",dp[K]);
    while (T--){
        int x,y,c;
        cin>>x>>y>>c;
        if(c==0){
            cout<<"accept\n";
            continue;
        }
        c--;
        if (b[x]+b[y]>dp[c]) printf("accept\n");
        else if (fabs(b[x]+b[y]-dp[c])<=eps) printf("both\n");
        else printf("reselect\n");
    }
    return 0;
}
复制代码

 

The 2021 CCPC Guangzhou Onsite

怎么感觉我打了一场表(

坐大牢

I. Pudding Store

打表找规律

复制代码
#include <bits/stdc++.h>
using namespace std;
const long long fish=998244353;
long long Pow(long long x,int y){
    long long ans=1;
    while (y){
    if (y&1) ans=1ll*ans*1ll*x%fish;
    x=1ll*x*1ll*x%fish;
    y>>=1;
    }
    return ans;
}
int main(){
    int T;
    cin>>T;
    while (T--){
        int N;
        scanf("%d",&N);
        if (N==1) printf("1\n");
        else if (N==2) printf("2\n");
        else if (N==3) printf("6\n");
        else printf("%lld\n",Pow(2,N-3)*6ll%fish);
    }
    return 0;
}
复制代码

H. Three Integers

不妨设

x=k1y+a

y=k2z+b

z=k3x+c

暴力消一消未知数

x=k1k2k3x+k2k3c+k3b+a

y=k2k3x+k2c+b

z=k3x+c

然后讨论一下k1,k2,k3,取合适的值就行了。

复制代码
#include <bits/stdc++.h>
using namespace std;
int T;
const long long mx=1e18;
int main(){
    cin>>T;
    while (T--){
        long long a,b,c,x,y,z;
        scanf("%lld%lld%lld",&a,&b,&c);
        if (a==b&&b==c&&a==0){
            printf("YES\n");
            printf("1 1 1\n");
            continue;
        }
        if (a==b&&b==c){
            printf("NO\n");
            continue;
        }
        if (a>c){
            long long k1=0,k3=max(0ll,(b-c)/a)+1;
            long long k2=max((a-b)/(k3*a+c),0ll)+1;
             x=a,y=k2*k3*a+k2*c+b,z=k3*a+c;
             if (x<=mx&&y<=mx&&z<=mx) printf("YES\n%lld %lld %lld\n",x,y,z);
             else printf("NO\n");
             continue;
        }else
        if (b>a){
            long long k1=max((c-a)/b,0ll)+1,k2=0;
            long long k3=max((b-c)/(k1*b+a),0ll)+1;
            x=k1*b+a;y=b;z=k3*x+c;
            if (x<=mx&&y<=mx&&z<=mx) printf("YES\n%lld %lld %lld\n",x,y,z);
            else printf("NO\n");
            continue;
        }else
        if (c>b){
            long long k3=0,k2=max(0ll,(a-b)/c)+1;
            long long k1=max((c-a)/(k2*c+b),0ll)+1;
            z=c,y=k2*c+b,x=k1*k2*c+k1*b+a;
            if (x<=mx&&y<=mx&&z<=mx) printf("YES\n%lld %lld %lld\n",x,y,z);
            else printf("NO\n");
            continue;
        }
        else printf("NO\n");
    }
    return 0;
}
复制代码

F. Cactus

毒瘤。

想了半天仙人掌计数怎么做,然后发现没什么想法。

看了看榜发现过了一车人,遂自闭。

人类智慧跑了前几个f,写了个暴力代码一跑发现答案长得很像斐波那契数列

让队友帮忙重新算了一遍前几个f,发现不太一样,遂扔进暴力代码

发现跑出来还是一样的?

于是大胆猜测f没啥用,直接输出斐波那契数列。

证明好像是用拉格朗日插值法展开,不太懂.jpg

复制代码
#include <bits/stdc++.h>
using namespace std;
int N;
int f[300005];
const int fish=998244353;
int main(){
    cin>>N;
    f[1]=1,f[2]=1,f[3]=2;
    for (int i=3;i<=N;i++){
        f[i]=(f[i-1]+f[i-2])%fish;
    }
    cout<<f[N]<<endl;
}
复制代码

 The 2021 CCPC Guilin Onsite (Grand Prix of EDG)


感觉还行

A.A Hero Named Magnus

 

猛犸不上ban

签到,输出2x1即可。记得long long

代码不放了

 

D. Assumption is All You Need

从前往后做。

贪心的想,对于一个位置来说,它只能和后面比它小的位置换

所以我们希望大的尽可能考前

n只有2000,每次暴力找第一个下一个比当前位置小的数字然后交换,直到ai=bi

复制代码
#include <bits/stdc++.h>
using namespace std;
int N;
int a[5005],b[5005];
vector<pair<int,int> >ans;
int main(){
    int T;
    cin>>T;
    while (T--){
    ans.clear();
    scanf("%d",&N);
    for (int i=1;i<=N;i++)
        scanf("%d",&a[i]);
    for (int i=1;i<=N;i++)
        scanf("%d",&b[i]);
    bool flag=false;
    for (int i=1;i<=N;i++){
        if (a[i]==b[i]) continue;
        if (a[i]<b[i]) {flag=true;break;}
        while (a[i]>b[i]){
        for (int j=i+1;j<=N;j++)
            if (a[i]>a[j]&&a[j]>=b[i]){
                swap(a[i],a[j]);
                ans.push_back({i,j});
            }
        }
    }
        if (flag){
            printf("-1\n");
        }
        else{
        printf("%d\n",ans.size());
        for (auto x:ans)
            printf("%d %d\n",x.first,x.second);
        }
    }
    return 0;
}
复制代码

G. Occupy the Cities

显然答案具有单调性,考虑二分

然后每个位置能1到的最左点和最右点是可以确定的。

但是因为第一次要把1变成11,所以有一边会少一个

暴力判断即可。

复制代码
#include <bits/stdc++.h>
using namespace std;
int N;
char c[1000005],c1[1000005];
bool Check(int ls){
    for (int i=1;i<=N;i++)    
        c[i]=c1[i];
    int lst=0;
    for (int i=1;i<=N;i++){
        if (c[i]=='1'){
            if (i-lst-1>ls) return false;
            if (lst>i){
                int pos=i+ls;
                lst=max(lst,pos);
                if (pos>=N) return true;
            }
            else
            if (i-lst-1<=ls-1){
                int pos=i+ls;
                lst=max(ls,pos);
                if (pos>=N) return true;
            }
            else{    
                int pos=i+ls-1;
                lst=max(lst,pos);
                if (pos>=N) return true; 
            }
            lst=max(lst,i);
        }
    }
    if (lst>=N) return true;else
    return false;
}
int main(){
    int T;
    cin>>T;
    while (T--){
    scanf("%d",&N);
    for (int i=1;i<=N;i++) cin>>c1[i];
    int l=0,r=N;
    int ans=0;
    while (l<=r){
        int mid=(l+r)>>1;
        if (Check(mid)) ans=mid,r=mid-1;
        else l=mid+1;
        }
    cout<<ans<<'\n';
    }
    return 0;
}
复制代码

K. Tax

发现n只有50

其实题目中最短路这个限制非常强。

考虑先跑一次最短路,然后把最短路相同的点归为一层,每层向下一层连边

对着建出来的新图跑个dfs即可。

为什么这是对的呢?

考虑这个分层图。

每次在图上做一次行走,相当于从一层的任意一个点,走向下一层的任意一个点

也就是说是ki ki表示第i层的节点数

显然,这个式子在ki尽可能相等的时候最大

于是假设n个点分了k组,就是nkk

算一下这个函数的最大值发现跑得过

复制代码
#include <bits/stdc++.h>
using namespace std;
int N,M;
int ans[55];
int cnt,nex[10005],Arrive[10005],qz[10005],las[10005],dis[55],cnt1,qz1[10005],w[10005],cntt[10005];
bool viss[55];
bool vis[55];
int nex1[10005],las1[10005],Arrive1[10005];
void jt(int x,int y,int c){
    cnt++;
    nex[cnt]=las[x];
    las[x]=cnt;
    Arrive[cnt]=y;
    qz[cnt]=c;
}
void Getdis(){
    memset(dis,63,sizeof(dis));
    dis[1]=0;
    queue<int> bfs;
    memset(vis,0,sizeof(vis));
    vis[1]=1;
    bfs.push(1);
    while (!bfs.empty()){
        int x=bfs.front();
        bfs.pop();
        vis[x]=false;
        for (int i=las[x];i;i=nex[i]){
            int v=Arrive[i];
            if (dis[x]+1<=dis[v]){
                dis[v]=dis[x]+1;
                if (!vis[v]) bfs.push(v);
            }
        }
    }
}
void jt1(int x,int y,int c){
    cnt1++;
    nex1[cnt1]=las1[x];
    las1[x]=cnt1;
    Arrive1[cnt1]=y;
    qz1[cnt1]=c;
}
void dfs(int Now,int nw){
    //cout<<Now<<endl;
    viss[Now]=true;
    ans[Now]=min(ans[Now],nw);
    for (int i=las1[Now];i;i=nex1[i]){
        int v=Arrive1[i];
        if (viss[v]) continue;
        int col=qz1[i];        
        cntt[col]++;
        nw=nw+w[col]*cntt[col];
        dfs(v,nw);
        nw=nw-w[col]*cntt[col];
        cntt[col]--;
    }
    viss[Now]=false;
}
int main(){
    scanf("%d%d",&N,&M);
    for (int i=1;i<=M;i++) scanf("%d",&w[i]);
    for (int i=1;i<=M;i++){
        int u,v,c;
        scanf("%d%d%d",&u,&v,&c);
        jt(u,v,c);
        jt(v,u,c);
    }
    Getdis();
    for (int u=1;u<=N;u++){
        for (int i=las[u];i;i=nex[i]){
            int v=Arrive[i];
            if (dis[u]+1==dis[v]){    
                jt1(u,v,qz[i]);
            }
        }
    }
    memset(ans,63,sizeof(ans));
    dfs(1,0);
    for (int i=2;i<=N;i++)
        cout<<ans[i]<<'\n';
    return 0;
} 
复制代码

 

posted @   si_nian  阅读(152)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?
欢迎阅读『寒假训练(复健)记录』
点击右上角即可分享
微信分享提示