Atcoder ABC 190 赛后解题报告

Atcoder ABC 190 赛后解题报告

最后 40min 都没搞出来 E 我也是服了。

A - Very Very Primitive Game /

首先理解题意,不是谁先吃完谁赢,而是谁先吃完,谁输。

然后就简单了,注意在 \(a=b\) 是要考虑谁是先手,先手必输。

//Don't act like a loser.
//This code is written by huayucaiji
//You can only use the code for studying or finding mistakes
//Or,you'll be punished by Sakyamuni!!!h>
#define int long long
using namespace std;

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

int a,b,c;

signed main() {
	cin>>a>>b>>c;
	if(c==1) {
		if(a>=b) {
			printf("Takahashi\n");
		}
		else {
			printf("Aoki\n");
		}
	}
	else {
		if(a>b) {
			printf("Takahashi\n");
		}
		else {
			printf("Aoki\n");
		}
	}
	return 0;
}

B - Magic 3

这个题非常简单,对于每一组魔法,把 \(X_i,Y_i\)\(S,D\) 做大小比较即可,注意两者只要有一种不满足条件魔法就失效。

//Don't act like a loser.
//This code is written by huayucaiji
//You can only use the code for studying or finding mistakes
//Or,you'll be punished by Sakyamuni!!!
#include<bits/stdc++.h>
#define int long long
using namespace std;

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

const int maxn=1e3+10;

int n,x[maxn],y[maxn],s,d;

signed main() {
	bool flag=0;
	cin>>n>>s>>d;
	for(int i=1;i<=n;i++) {
		x[i]=read();
		y[i]=read();
		if(x[i]<s&&y[i]>d) {
			cout<<"Yes\n";//有一组成功即可。
			return 0;
		}
	}
	cout<<"No\n";//都不成功输出 NO

	return 0;
}

C - Bowls and Dishes

这个题一看 \(k\) 那么小,直接搜索即可,我们枚举第 \(i\) 个客人把球放在第 \(C_i\) 或者 \(D_i\) 个盘子上的时候满足的条件数,总共 \(2^k\) 种情况用搜索来枚举,最后写一个 calc 函数检查有多少条件被满足即可,取最大值。

时间复杂度 \(O(2^k\cdot m)\)

//Don't act like a loser.
//This code is written by huayucaiji
//You can only use the code for studying or finding mistakes
//Or,you'll be punished by Sakyamuni!!!
//#pragma GCC optimize("Ofast","-funroll-loops","-fdelete-null-pointer-checks")
//#pragma GCC target("ssse3","sse3","sse2","sse","avx2","avx")
#include<bits/stdc++.h>
using namespace std;

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

const int maxn=1e2+10;

int n,m,k,a[maxn],b[maxn],ans,c[maxn],d[maxn],hv[maxn];
//hv 记录第 i 个盘子上有多少个球

int calc() {
	int ret=0;
	for(int i=1;i<=m;i++) {
		if(hv[a[i]]&&hv[b[i]]) {
			ret++;//满足条件
		}
	}
	return ret;
}

void dfs(int i) {
	if(i==k+1) {
		ans=max(ans,calc());
		return ;
	}
	
	hv[c[i]]++;
	dfs(i+1);
	hv[c[i]]--;//别忘了回溯
	hv[d[i]]++;
	dfs(i+1);
	hv[d[i]]--;
	return ;
}

signed main() {
	cin>>n>>m;
	for(int i=1;i<=m;i++) {
		a[i]=read();
		b[i]=read();
	}
	
	cin>>k;
	for(int i=1;i<=k;i++) {
		c[i]=read();
		d[i]=read();
	}
	
	dfs(1);
	cout<<ans<<endl;

	return 0;
}

D - Staircase Sequence

这是一道数学题。

我们首先可以分析一下样例,对于 \(N=12\) 时,有 \(4\) 组解。

  • \([12]\)
  • \([3,4,5]\)
  • \([−2,−1,0,1,2,3,4,5]\)
  • \([−11,−10,−9,\cdots,10,11,12]\)

我们发现,第 \(3\) 组,第 \(4\) 组解的后缀和第 \(2\) 组,第 \(1\) 组解完全相同。因此我们可以得出以下结论:

如果对于整数 \(N\),有一组解 \([x,x+1,x+2,\cdots,x+k]\)\(x>0\),那么一定还有一组解为 \([-(x-1),-(x-2),\cdots ,0,\cdots x-1,x,x+1\cdots,x+k]\),证明过于简单,这里不再赘述。

那么很明了了,我们只需要求出所有 \(x>0\) 的情况数即可,可是怎么求呢?

我们再从和为 \(N\) 这个条件出发想,如果有一组解有偶数个数,如:\([x-k,\cdots,x,x+1,\cdots,x+1+k]\),一定有 \((x+0.5)*(2k+2)=N\),同理,若有奇数个数,如 \([x-k,\cdots,x,\cdots,x+k]\),一定有 \(x*(2k+1)=N\)。证明略。

我们可以枚举某一组解的长度,确定 \(k\)\(x\),当 \(x-k\leq 0\) 时,枚举结束即可。可以证明,若序列长度为 \(l\),且 \(x-k>0\),一定有 \(2*l^2\leq N\),证明略。

前面提到的证明非常基础,若不能证明,建议补补数学。

//Don't act like a loser.
//This code is written by huayucaiji
//You can only use the code for studying or finding mistakes
//Or,you'll be punished by Sakyamuni!!!
#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,ans;

signed main() {
	cin>>n;
	for(int i=1;i*i<=2*n;i++) {
        //这里可能和前面所说的变量名和判断方法不太一样
        //希望读者自行理解
		if(i&1&&n%i==0) {
			ans++;
		}
		if(i%2==0&&(n*2)%i==0&&n%i!=0) {
			ans++;
		}
	}
	cout<<ans*2<<endl;
	return 0;
}

记得开 long long

E - Magical Ornament

本题是这场比赛个人认为最难的一道题。

首先容易想到的是用图论模型来转化问题。我们可以把可以相邻放的颜色用一条无向边连起来,边权为 \(1\)。这样 \(u,v\) 两种颜色连接起来的最小长度就是 \(mindist(u,v)+1\)。要 \(+1\) 是因为两个端点都算在长度里面。显然,我们要对于每一个 \(C_i\),把它作为起点,求一遍最短路,用堆优化 dijsktra 即可。如果您相信 SPFA 还没死,您可以尝试用一下,笔者还没有试过。试试就逝世

然后呢?比赛的时候到了这里,我就做不下去了,我尝试用了一个小根堆贪心,失败了。其实正解藏在1数据范围里。由于 \(k\leq 17\),可以想到用的是状压 DP。

定状态

我们令 \(f_{i,S}\) 为目前最后一个连好的球为 \(C_i\)\(S\) 表示的是 \(C\) 中还没连好的球的下标集合,所需要的最少的球数。

转移方程

\[f_{i,S}=\min\limits_{j\in S}{f_{j,S\operatorname{xor} 2^{i-1}}+mindist(C_j,C_j)} \]

\(f_{j,S\operatorname{xor} 2^{i-1}}\) 表示还未加入 \(i\) 时的情况,既然要加入 \(C_i\),而之前的序列最后一个是 \(C_j\),必然要新加入的球数为 \(C_i,C_j\) 的最短距离。

边界&初始状态

\[f_{i,0}=1 \]

至少有一个球呀~

DP 用记忆化搜索会方便一点。

关于最短路径的存储,我们可以用 \(dist[i][j]\) 表示由 \(C_i\) 出发,到 \(j\) 的最短距离,因为我们只关心由 \(C\) 中的点出发的最短路径,空间符合要求。

判断 \(-1\) 的方法很多,但归根结底就是连通性的判断,在算最短路的时候,或者单独处理,都是可以的。

时间复杂度为 \(O(k\cdot n\log n+k^2\cdot 2^k)\)。我的评测记录里最慢的一条是 \(993ms\),而且还用了快读,可以说是有惊无险啊!

//Don't act like a loser.
//This code is written by huayucaiji
//You can only use the code for studying or finding mistakes
//Or,you'll be punished by Sakyamuni!!!
#include<bits/stdc++.h>
#define int long long
using namespace std;

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

const int maxn=1e5+10; 

int c[maxn];

struct edge {
	int v,w,next;
}e[maxn<<4];

int n,m,cnt,h[maxn],s,dis[20][maxn],k;
bool vis[maxn],calc[20][1<<20];
int f[20][1<<20];

void addedge(int u,int v,int w) {
	e[++cnt].v=v;
	e[cnt].w=w;
	e[cnt].next=h[u];
	h[u]=cnt;
}
void insert(int u,int v,int w) {
	addedge(u,v,w);
	addedge(v,u,w);
}

void dijsktra(int s) {
	set<pair<int,int> > q;
	fill(dis[s],dis[s]+n+1,0x7fffffff);
	fill(vis,vis+n+1,0);
	vis[c[s]]=1;
	dis[s][c[s]]=0;
	q.insert(make_pair(0LL,c[s]));
	while(!q.empty()) {
		pair<int,int> pr=*q.begin();
		q.erase(q.begin());
		int u=pr.second;
		vis[u]=1;
		for(int i=h[u];i;i=e[i].next) {
			int v=e[i].v;
			if(vis[v]) continue;
			if(dis[s][u]+e[i].w<dis[s][v]) {
				set<pair<int,int> >::iterator it=q.find(make_pair(dis[s][v],v));
				if(it!=q.end()) {
					q.erase(it);
				}
				dis[s][v]=dis[s][u]+e[i].w;
				q.insert(make_pair(dis[s][v],v));
			}
		}
	}
	return ;
}

int solve(int u,int state) {
	if(state==0) {
		return 0;
	}
	if(calc[u][state]) {
		return f[u][state];
	}
	calc[u][state]=1;
	f[u][state]=0x7fffffff;
	
	for(int i=1;i<=k;i++) {
		if((1<<(i-1))&state) {
			f[u][state]=min(solve(i,state^(1<<(u-1)))+dis[i][c[u]],f[u][state]);
		}
	}
	return f[u][state];
}

signed main() {
	cin>>n>>m;
	for(int i=1;i<=m;i++) {
		insert(read(),read(),1);
	}
	cin>>k;
	for(int i=1;i<=k;i++) {
		c[i]=read();
		dijsktra(i);
	}
	
	for(int i=1;i<=k;i++) {
		for(int j=1;j<i;j++) {
			if(i!=j) {
				if(dis[i][c[j]]>1e9) {
					cout<<-1<<endl;
					return 0;
				}
			}
		}
	}
	
	int ans=0x7fffffff;
	for(int i=1;i<=k;i++) {
		ans=min(ans,solve(i,(1<<k)-1));
	}
	
	cout<<ans+1<<endl;
	return 0;
}

F - Shift and Inversions

这个题其实蛮简单的

我们首先需要理解的几乎是他所说的这个序列到底是个怎么样的序列。不难发现,其实就是每一次把序列向左移动一个单位,第一个数移到最后面。发现了这个,你就成功了一半!

我们先可以用最基础的树状数组求出原数列的逆序对个数,很简单,不再赘述。

其次,我们可以考虑由于序列变换而产生的逆序对变化量。假设现在的序列为 \(a_1,a_2,a_3,\cdots,a_n\),那么变换完过后就成为了 \(a_2,a_3,\cdots,a_n,a_1\)。我们发现 \(a_2\)\(a_n\) 这一段的序列并没有发生变化,因此这一段的你许多数量不会改变,我们只需要考虑 \(a_1\) 带来的变化。由于这是一个全排列,我们的工作量少了很多,否则我们还要再用树状数组来计算比每个数大的数有多少个,比它小的数又有多少个。由于 \(a_1\) 原来在队首,它和比它小的数会产生逆序对,而移到最后时,只会和比它大的数产生逆序对,这样一加一减。就可以轻松维护处答案了。

记得开 long long

//Don't act like a loser.
//This code is written by huayucaiji
//You can only use the code for studying or finding mistakes
//Or,you'll be punished by Sakyamuni!!!
#include<bits/stdc++.h>
#define int long long
#define lowbit(x) x&(-x) 
using namespace std;

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

const int maxn=3e5+10;

struct node {
	int num,pos;
}p[maxn];
bool cmp(node a,node b) {
	if(a.num!=b.num)
		return a.num>b.num;
	return a.pos>b.pos;
}

int c[maxn],a[maxn],n;

void add(int x,int num) {
	for(;x<=n;x+=lowbit(x)) {
		c[x]+=num;
	}
}
int query(int x) {
	int ans=0;
	
	for(;x;x-=lowbit(x)) {
		ans+=c[x];
	}
	return ans;
}

signed main() {
	cin>>n;
	for(int i=1;i<=n;i++) {
		a[i]=read();
		p[i].num=a[i];
		p[i].pos=i;
	}
	sort(p+1,p+n+1,cmp);
	
	long long ans=0;
	for(int i=1;i<=n;i++) {
		add(p[i].pos,1);
		ans+=query(p[i].pos-1);
	}
	
	for(int i=1;i<=n;i++) {
		printf("%lld\n",ans);
		ans=ans+(n-1-a[i])-a[i];//维护答案
	}

	return 0;
}
原文地址:https://www.cnblogs.com/huayucaiji/p/ABC190.html