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,那么两种转移方式就为
对于每个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;
}
点击查看代码
#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;
}