The 2021 ICPC Asia Nanjing Regional Contest

The 2021 ICPC Asia Nanjing Regional Contest


A Oops, It’s Yesterday Twice More

点击查看代码
#include <bits/stdc++.h>
using namespace std;

string s[5]={"","UL","UR","DL","DR"};

int main(){
    #ifdef lmj_debug
        freopen("1.in","r",stdin);
    #endif
    int n,A,B;
    cin>>n>>A>>B;
    int opt=0,dist=100000000;
    int a[5][5]={{0,0},{1,1},{1,n},{n,1},{n,n}};
    for (int i=1;i<=4;i++){
        int t=abs(a[i][0]-A)+abs(a[i][1]-B);
        if(t<dist){
            opt=i;
            dist=t;
        }
    }
    for (int i=1;i<n;i++) printf("%c",s[opt][0]);
    for (int i=1;i<n;i++) printf("%c",s[opt][1]);
    if(A>a[opt][0]){
        for (int i=1;i<A;i++) printf("D");
    }else if(A<a[opt][0]){
        for (int i=n;i>A;i--) printf("U");
    }
    if(B>a[opt][1]){
        for (int i=1;i<B;i++) printf("R");
    }else if(B<a[opt][1]){
        for (int i=n;i>B;i--) printf("L");
    }
    return 0;
}


D Paimon Sorting

题意:给一个排序的代码,问按照该排序代码,做前缀\(A_k\)的时候,交换次数是多少
做法:这种题目一般我们都考虑以每个元素的贡献来看,简单情况考虑:所有数都不相等,此时我们对于一个数,找到大于他的第一个数,我们称这个数为特殊数,那么很容易知道,这个数的贡献是2,因为这个数必然会被交换两次,而对于其它普通的数贡献就是目前位置大于它的数的个数,但如果存在相同的数,那么对于特殊的数任然不变,但是对于普通的数,前面比它大的,但是相同的元素只会发生一次交换,因此需要对元素去重一下再统计。但是特殊情况是,两个特殊的数中间,夹了一个普通的数,但是这个普通的数等于前一个特殊的数,这种就会发生以下情况

6 6 5 5 5 10

10 6 5 5 5 6 
6 10 5 5 5 6 
5 10 6 5 5 6 
5 6 10 5 5 6 
5 5 10 6 5 6 
5 5 6 10 5 6 
5 5 5 10 6 6 
5 5 5 6 10 6
5 5 5 6 6 10

这组数由于6和10之间还夹了一个6,所以我们发现其实从第二个6到10之间这些数都被多了一次贡献,所以还需要统计这个,这是因为普通的6替代了原来6让后面每个数都要多交换一次

点击查看代码
#include <bits/stdc++.h>
using namespace std;

const int maxn=2e5+10;

int a[maxn];
int c[maxn];
int N;

int ask(int x){
    int ans=0;
    for (;x;x -= x&-x) ans+=c[x];
    return ans;
}

void add(int x,int y){
    for (;x<=N;x+= x&-x) c[x]+=y;
}

int query(int l,int r){
    return ask(r)-ask(l-1);
}

bool vis[maxn];

void init(int x){
    for (int i=1;i<=x;i++) vis[i]=0,c[i]=0;
}

int main(){
    #ifdef lmj_debug
        freopen("1.in","r",stdin);
    #endif
    int T=0;
    cin>>T;
    while(T--){
        int n;scanf("%d",&n);N=n;
        init(n);
        for (int i=1;i<=n;i++) scanf("%d",&a[i]);
        for (int i=1;i<=n;){
            int j=i;
            for (;j<=n;j++){
                if(a[j]>a[i]) {break;}
            }
            if(j==n+1) break;
            vis[j]=1;
            i=j;
        }
        long long ans=0;
        int last=a[1];
        printf("0");
        add(a[1],1);
        int cnt=0;
        for (int i=2;i<=n;i++){
            if(vis[i]){ 
                ans+=2;
                last=a[i];
                ans+=cnt;
                cnt=0;
            }
            else {
                if(cnt) cnt++;
                ans+=query(a[i]+1,n);
                if(a[i]==last && !cnt) cnt++; 
            }
            int t=query(a[i],a[i]);
            if(!t) add(a[i],1);
            printf(" %lld",ans);
        }
        puts("");
    }
    return 0;
}

M. Windblume Festival

分正负考虑就可以,注意只有一个值的情况

点击查看代码
#include <bits/stdc++.h>
using namespace std;

const int maxn=1e6+10;
int a[maxn];

int main(){
    #ifdef lmj_debug
        freopen("1.in","r",stdin);
    #endif
    int n;
    int T;
    cin>>T;
    while(T--){
        scanf("%d",&n);
        int flag1=0,flag2=0;
        long long sum=0;
        for (int i=1;i<=n;i++) {
            scanf("%d",&a[i]);
            sum+=a[i];
            if(a[i]>=0) flag1=1;
            else if(a[i]<0) flag2=1;
        }
        if(n==1){
            printf("%d\n",a[1]);
            continue;
        }
        if(flag1 && !flag2){
            long long ans=0;
            for (int i=1;i<=n;i++) ans=max(ans,sum-2*a[i]);
            printf("%lld\n",ans);
        }else if(flag1 && flag2){
            long long ans=0;
            for (int i=1;i<=n;i++) ans+=abs(a[i]);
            printf("%lld\n",ans);
        }else {
            sum=abs(sum);
            long long ans=0;
            for (int i=1;i<=n;i++) ans=max(ans,sum+2*a[i]);
            printf("%lld\n",ans);
        }
    }
    return 0;
}

C. Klee in Solitary Confinement

题意:给定n个数,可以有一次修改机会,将l,r中的每个数+k,请问该序列众数最大是多少
做法:首先不是把连续的段修改了就行的,虽然样例是这样,但是其实是不对的,因为我们可以修改不是连续的数的一段,从而把a[i]+k的值增加,那么其实做法就是我们单独对每一个a[i]和a[i]-k来处理,因为a[i]的答案只可能从a[i]-k中来,所以我们设一段中出现的a[i]的贡献为-1,a[i]-k的贡献为1,比如,k=2,时,1 3 3 3 1 3,就为1 -1 -1 -1 1 -1,那么我们其实就是求这个序列的最大连续子段和再加上原来a[i]出现次数就好了可以用一个vector来维护,复杂度O(n)

点击查看代码
#include <bits/stdc++.h>
using namespace std;

const int maxn=1e6+10;

int a[maxn*2];
int vis[maxn*2];
vector<int>G[maxn*2],G2[maxn*2];

inline int solved(int x,int k){
    if(x-k<0 || x-k>=maxn*2 || G[x-k].size()==0 || k==0) return G[x].size();
    int ans=G[x].size();
    int p=ans;
    int l=0,r=0,sum=0;
    vector<int>c;
    c=G2[x];
    for (r=0;r<c.size();r++){
        sum+=c[r];
        while(sum<0 && l<=r) {sum-=c[l];l++;}
        ans=max(ans,sum+p);
    }
    return ans;
}

int main(){
    #ifdef lmj_debug
        freopen("1.in","r",stdin);
    #endif
    int T;
        int n,k;
        int ans=0;
        scanf("%d%d",&n,&k);
        for (int i=1;i<=n;i++) {
            scanf("%d",&a[i]);
            a[i]+=(maxn-5);
            G[a[i]].push_back(i);
            G2[a[i]].push_back(-1);
            if(a[i]+k<maxn*2 && a[i]+k>=0)
            G2[a[i]+k].push_back(1);
        }
        for (int i=1;i<=n;i++){
            if(!vis[a[i]]){
                vis[a[i]]=1;
                ans=max(ans,solved(a[i],k));
            }
        }
        printf("%d\n",ans);
    return 0;
}

H. Crystalfly(树形dp)

做法:考虑到t<=3,那么走法其实就只有两种,一种是进入某个儿子 v 后继续向下,这样 x 的所有其它儿子的蝴蝶都无法被获取。另一种是进入某个儿子 v 获取 w[v] 后立即回到 x ,然后进入另一个儿子 u 并获得 u ,这要求 t[u]=3,那么就可以设\(dp[x]\)不取x点的贡献,从x的子树取到的最大值,sum[x]表述 \(\sum{dp[v]}\),v is son of x,那么两种转移方式就为

\[dp[x]=max(dp[x],sum[x]+w[v]) \]

\[dp[x]=max(dp[x],sum[x]-dp[v]+sum[v]+w[v]+w[u]),t[u]=3 \]

对于每个v维护一个最大的u即可

点击查看代码
#include <bits/stdc++.h>
using namespace std;

const int maxn=1e5+10;
typedef long long ll;

ll dp[maxn],sum[maxn];
ll w[maxn];
int t[maxn];
vector<int>G[maxn];

void init(int n){
    for (int i=1;i<=n;i++) G[i].clear(),dp[i]=sum[i]=w[i]=t[i]=0;

}

void dfs(int x,int f){
    ll tt=0;
    ll maxx[3],maxid[3];
    memset(maxx,0,sizeof(maxx));
    memset(maxid,0,sizeof(maxid));
    for (auto v:G[x]){
        if(v==f) continue;
        dfs(v,x);
        sum[x]+=dp[v];
        dp[x]+=dp[v];
        tt=max(tt,w[v]);
        if(t[v]==3){
            if(maxx[1]<w[v]){
                maxx[2]=maxx[1];
                maxid[2]=maxid[1];
                maxx[1]=w[v];
                maxid[1]=v;
            }else if(maxx[2]<=w[v]){
                maxx[2]=w[v];
                maxid[2]=v;
            }
        }
    }
    ll tt2=0;
    for (auto v:G[x]){
        if(v==f) continue;
        ll p=w[v]+sum[v]-dp[v];
        if(maxid[1]==v) p+=maxx[2];
        else p+=maxx[1];
        tt2=max(tt2,p);
    }
    dp[x]+=max(tt,tt2);
    return ;
}

int main(){
    #ifdef lmj_debug
        freopen("1.in","r",stdin);
    #endif
    int T;
    cin>>T;
    while(T--){
        int n;
        cin>>n;
        init(n);
        for (int i=1;i<=n;i++) scanf("%lld",&w[i]);
        for (int i=1;i<=n;i++) scanf("%d",&t[i]);
        for (int i=1;i<n;i++){
            int u,v;
            scanf("%d%d",&u,&v);
            G[u].push_back(v);
            G[v].push_back(u);
        }
        dfs(1,1);
        printf("%lld\n",dp[1]+w[1]);
    }
    return 0;
}

J. Xingqiu's Joke

题意:给定a,b两个数,支持三个操作,可以将其+1,-1,除以公约数,要求公约数为素数,问至少多少次操作可以将其中一个数变为1
做法:设a为大者,我们考虑到d=a-b,如果使用前两个操作,那么不会改变d,而且如果g为a,b的约数,那么g也一定为d的约数,而且考虑到\((<=1e9)\)的数的质因子不超过十个,所以我们直接找出d的公约素数记忆化搜索即可

点击查看代码
#include <bits/stdc++.h>
using namespace std;

typedef long long ll;

const ll mod=1e9;

inline ll mp(ll a,ll b) {
    return a*mod+b;
}

vector<int>prime;
unordered_map< long long ,int >G;

int dfs(int a,int b){
    if(a==1 || b==1){
        return G[mp(a,b)]=0;
    }
    if(G.find(mp(a,b))!=G.end()) return G[mp(a,b)];
    int ans=b-1;
    if(a-b==1) {
        ans=b-1;
        G[mp(a,b)]=ans;
        return ans;
    }
    int d=a-b;
    for (auto x:prime){
        if(d%x==0){
            int p=a%x;
            int p1=x-p;
            ans=min(ans,1+p+dfs((a-p)/x,(b-p)/x));
            ans=min(ans,1+p1+dfs((a+p1)/x,(b+p1)/x));
        }
    }
    G[mp(a,b)]=ans;
    return ans;
}

int main(){
    #ifdef lmj_debug
        freopen("1.in","r",stdin);
    #endif
    int T;
    cin>>T;
    while(T--){
        int a,b;
        cin>>a>>b;
        G.clear();
        prime.clear();
        if(a<b) swap(a,b);
        int d=a-b;
        for (int i=2;i<=sqrt(d);i++){
            if(d%i==0){
                while(d%i==0) d/=i;
                prime.push_back(i);
            }
        }
        if(d>1) prime.push_back(d);
       // prime.push_back(1);
        dfs(a,b);
        printf("%d\n",G[mp(a,b)]);
    }
    return 0;
}

I. Cloud Retainer's Game

做法:我们考虑到y=x的线上的点和y=-x上的点可以分别表示为(2H-y+x)%2H=0,(x+y)%2H=0,也就是,每条线如果不考虑挡板反弹,只考虑y=H的线反弹的话,其上所有点满足以上关系,其实我们可以发现,每个点都可以满足在一条y=ax+b,(a=-1,a=+1,0<=b<2*H),这就可以处理挡板反弹的情况了,因为反弹以后就相当于改变了截距,所以其实我们就可以设f(k)表示截距等于k的一条线段,所取到的最优答案,那么遇到点(x,y)时,\(f((2H-y+x)mod2H)\)\(f((x+y)mod2H)\)都加1,而遇到挡板(x,y)时,\(f((2H-y+x)mod2H)\)\(f((x+y) mod 2H)\)都取其中的最大值,某个状态只可能被x-1的状态更新到,所以按x排序更新即可,
因为要从(0,0)点出发,所以可以考虑写成从其他点出发,回到(0,0)点的答案,或者打个标记限定从(0,0)点出发,再求最值,类似求最短路

代码1:(0,0)出发

点击查看代码
#include <bits/stdc++.h>
using namespace std;

typedef long long ll;

const int maxn=5e5+10;

struct my{
    ll x,y,id;
};

my a[maxn];

bool cmp(const my x,const my y){
    if(x.x!=y.x) return x.x<y.x;
    return x.y<y.y;
}

unordered_map<ll,int>dp;

int main(){
    #ifdef lmj_debug
        freopen("1.in","r",stdin);
    #endif

    int T;
    cin>>T;
    while(T--){
        dp.clear();
        ll H;
        int n,m;
        scanf("%lld",&H);
        H=H*2ll;
        scanf("%d",&n);
        for (int i=1;i<=n;i++) {scanf("%lld%lld",&a[i].x,&a[i].y);a[i].id=1;}
        scanf("%d",&m);
        for (int i=1;i<=m;i++) {scanf("%lld%lld",&a[i+n].x,&a[i+n].y);}
        n+=m;
        sort(a+1,a+1+n,cmp);
        dp[0]=1;
        for (int i=1;i<=n;i++){
            if(a[i].id==0){
                if(dp[(a[i].x+a[i].y)%H]) dp[(a[i].x+a[i].y)%H]++;
                if(dp[(H+a[i].x-a[i].y)%H]) dp[(H+a[i].x-a[i].y)%H]++;
            }else {
                int x=dp[(a[i].x+a[i].y)%H];
                int y=dp[(H+a[i].x-a[i].y)%H];
                x=max(x,y);
                dp[(a[i].x+a[i].y)%H]=x;
                dp[(H+a[i].x-a[i].y)%H]=x;
            }
        }
        int ans=0;
        for (auto x:dp) ans=max(ans,x.second);
        printf("%d\n",ans-1);
        for (int i=1;i<=n;i++) a[i].id=0;
    }
    return 0;
}
代码2:回到(0,0)点
点击查看代码
#include <bits/stdc++.h>
using namespace std;

typedef long long ll;

const int maxn=5e5+10;

struct my{
    ll x,y,id;
};

my a[maxn];

bool cmp(const my x,const my y){
    if(x.x!=y.x) return x.x>y.x;
    return x.y>y.y;
}

unordered_map<ll,ll>dp;

int main(){
    #ifdef lmj_debug
        freopen("1.in","r",stdin);
    #endif

    int T;
    cin>>T;
    while(T--){
        dp.clear();
        ll H;
        int n,m;
        scanf("%lld",&H);
        H=H*2ll;
        scanf("%d",&n);
        for (int i=1;i<=n;i++) {scanf("%lld%lld",&a[i].x,&a[i].y);a[i].id=1;}
        scanf("%d",&m);
        for (int i=1;i<=m;i++) {scanf("%lld%lld",&a[i+n].x,&a[i+n].y);}
        n+=m;
        sort(a+1,a+1+n,cmp);
        for (int i=1;i<=n;i++){
            if(a[i].id==0){
                dp[(a[i].x+a[i].y)%H]++;
                dp[(H+a[i].x-a[i].y)%H]++;
            }else {
                int x=dp[(a[i].x+a[i].y)%H];
                int y=dp[(H+a[i].x-a[i].y)%H];
                x=max(x,y);
                dp[(a[i].x+a[i].y)%H]=dp[(H+a[i].x-a[i].y)%H]=x;
            }
        }
        ll ans=0;
        for (auto x:dp) ans=max(ans,x.second);
        printf("%lld\n",dp[0]);
        for (int i=1;i<=n;i++) a[i].id=0;
    }
    return 0;
}
posted @ 2022-03-29 14:42  lmj_1  阅读(331)  评论(0)    收藏  举报