Atcoder ABC 189 赛后解题报告(A-F)

Atcoder ABC 189 赛后解题报告

A - Slot

本题不难。我们可以随机选择一个字符作为基准,然后扫一遍字符串逐一比对就可以知道是否符合要求了。

//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;
}

int n;
string s;

signed main() {
	cin>>s;
	n=s.size();
	for(int i=0;i<n;i++) {
		if(s[i]!=s[0]) {
			cout<<"Lost\n";
			return 0;
		}
	}
	cout<<"Won\n";
	return 0;
}


B - Alcoholic

本题不难,但是卡精度卡得难受(连 long double 都卡):

QWQ

解法很简单,只要把每次酒精的量算出来,加起来,只要发现在某一时刻大于了 \(X\),就输出,并终止程序。否则输出 \(-1\)

既然他卡精度,我们只有一个解决办法就是避免小数的出现,我们只需要把 \(X\) 乘以 \(100\),就可以避免百分比的出现了。

//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;
const long double eps=1e-10 ;

int v[maxn],p[maxn];
int n;
int T,tot;

signed main() {
	cin>>n>>T;
	for(int i=1;i<=n;i++) {
		cin>>v[i]>>p[i];
		tot+=v[i]*p[i];
		if(T*100<tot) {//这里处理一下
			cout<<i<<endl;
			return 0;
		}
	}
	cout<<-1<<endl;
	return 0;
}


C - Mandarin Orange

本题难度比前两题大,比赛时稍微思考了一会儿(5min),发现这个题很简单。。。

  • 首先从时间复杂度入手。\(n\leq 10^4\),说明要么就是 \(O(n \log^3 n)\) 或者是 \(O(n^2)\) 带一点小优化。显然我们选择后者。
  • 其次我们想,这个题肯定没那么复杂,一定是个朴素的解法,我们从问题本质想,其实就是找一个区间,找到其最小值,这个最小值乘以区间长度的最大值就是答案。我们反过来想,可以先确定最小值,找出其可以对应的最大区间即可。

很有精神!我们只要扫一遍,把每个数作为最小值都往两边扩展一下区间,擂台法算最大值即可。

//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;


const int maxn=1e4+10; 

int n,a[maxn],ans;

signed main() {
	cin>>n;
	for(int i=1;i<=n;i++) {
		cin>>a[i];
	}
	for(int i=1;i<=n;i++) {
		int l=i,r=i;
		while(l-1>0&&a[l-1]>=a[i]) {
			l--;
		}
		while(r+1<=n&&a[r+1]>=a[i]) {
			r++;
		}
		ans=max(ans,(r-l+1)*a[i]);
	}
	cout<<ans<<endl;
	return 0;
}


D - Logical Expression

说实话,这个题,\(N\leq 60\) 这个条件给得妙啊,成功让我在比赛时对我的线性算法怀疑了10min。。。。

与空气斗智斗勇

咳咳,闲言少叙,我们回归正题。我们尝试用 DP 来解决问题。

定状态

我们令 \(f_{i,0/1}\) 为进行完了前 \(i\) 次操作,结果为 \(0/1\) 的方案个数。

列转移方程

\[\begin{cases} f_{i,0}=f_{i-1,1}+2\cdot f_{i-1,0}&s_i=AND\\ f_{i,1}=f_{i-1,1}&s_i=AND\\ f_{i,0}=f_{i-1,0}&s_i=OR\\ f_{i,1}=f_{i-1,0}+2\cdot f_{i-1,1}&s_i=OR \end{cases}\]

很好理解,如果理解不了建议补一补布尔运算。

定初始状态

我们的初始状态只有 \(f_{0,0}\)\(f_{0,1}\)。显然,他们都等于 \(1\)

最后答案为 \(f_{n,1}\)

//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>
using namespace std;

const int maxn=61;

int n,f[maxn][2];
string s[maxn];

signed main() {
	cin>>n;
	for(int i=1;i<=n;i++) {
		cin>>s[i];
	}
	f[0][0]=f[0][1]=1;
	for(int i=1;i<=n;i++) {
		if(s[i][0]=='A') {
			f[i][1]=f[i-1][1];
			f[i][0]=f[i-1][1]+f[i-1][0]*2;
		}
		if(s[i][0]=='O') {
			f[i][1]=f[i-1][1]*2+f[i-1][0];
			f[i][0]=f[i-1][0];
		}
	}
	
	cout<<f[n][1]<<endl;
	return 0;
}


哪个大佬闲得蛋疼可以加强一下数据在评论区回复题目链接。

E - Rotate and Flip

本题有一个重要切入点,就是操作的顺序不会改变,这就提示,我们每一次变换完过后的变化是可以描述,且可叠加的。

  1. 先看旋转,设点 \(A(x,y)\),顺时针有:

那么 \(A'(y,-x)\)

  1. 同理逆时针旋转后 \(A'(-y,x)\)
  2. 沿直线 \(x=p\) 翻折得 \(A'(2p-x,y)\)
  3. 同理,沿直线 \(y=p\) 翻折得 \(A'(x,2p-y)\)

既然操作可以叠加,我们最后的坐标一定是可以在 \(O(1)\) 之内求解的,总时间复杂度 \(O(n+m+q)\)

//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=2e5+10;

int n,m,q,x[maxn],y[maxn];
struct op {
	int rev,mulx,muly,addx,addy;
}p[maxn];

signed main() {
	cin>>n;
	for(int i=1;i<=n;i++) {
		x[i]=read();y[i]=read();
	}
	cin>>m;
	p[0].mulx=p[0].muly=1;
	for(int i=1;i<=m;i++) {
		int t,v;
		t=read();
		if(t==1) {
			p[i].rev=p[i-1].rev^1;
			p[i].addx=p[i-1].addy;
			p[i].addy=-p[i-1].addx;
			p[i].mulx=p[i-1].muly;
			p[i].muly=-p[i-1].mulx;
		}
		if(t==2) {
			p[i].rev=p[i-1].rev^1;
			p[i].addx=-p[i-1].addy;
			p[i].addy=p[i-1].addx;
			p[i].mulx=-p[i-1].muly;
			p[i].muly=p[i-1].mulx;
		}
		if(t==3) {
			p[i].rev=p[i-1].rev;
			v=read();
			p[i].addx=2*v-p[i-1].addx;
			p[i].addy=p[i-1].addy;
			p[i].mulx=-p[i-1].mulx;
			p[i].muly=p[i-1].muly;
		}
		if(t==4) {
			p[i].rev=p[i-1].rev;
			v=read();
			p[i].addx=p[i-1].addx;
			p[i].addy=2*v-p[i-1].addy;
			p[i].mulx=p[i-1].mulx;
			p[i].muly=-p[i-1].muly;
		}
	}
	cin>>q;
	for(int i=1;i<=q;i++) {
		int a=read(),b=read();
		if(!p[a].rev)
			printf("%lld %lld\n",p[a].mulx*x[b]+p[a].addx,p[a].muly*y[b]+p[a].addy);
		else {
			printf("%lld %lld\n",p[a].mulx*y[b]+p[a].addx,p[a].muly*x[b]+p[a].addy);
		}
	}
	return 0;
}

码农题

F - Sugoroku2

这个题,我一眼,就看出来是个期望DP(废话)。

但就是在比赛时状态列得不好,挂掉了。。。。

总之我们还是一步一步来接这个问题。

定状态

我们设 \(f_i\) 为从 \(i\) 出发,到达 \(n\) 所需要的期望步数。

我在比赛设的是 \(f_i\) 表示由 \(0\) 出发到 \(i\) 的期望步数,这样不好处理退回到 \(0\) 的情况,因此我们采取上面一种。

列转移方程

\[\begin{cases} f_i=\frac{1}{m}\cdot\sum\limits_{k=1}^m f_{i+k}&\forall x\ a_x\neq i\\ f_i=f_0&a_x=i \end{cases}\]

也就是说如果我到了 \(i\) 要回到 \(0\),那么就要重新来过了。

很显然,这个转移方程不能直接计算,而是要解方程。我们很难直接入手。

我们可以这样想,我们最后求解的是 \(f_0\),而因为 \(f_0\) 的出现,导致不能直接计算,我们可以把 \(f_0\) 作为一个未知数,尝试用 \(f_0\) 来表示其他数,也就是说,\(1\leq i\leq n\) 时,\(f_i=p_i\cdot f_0+q_i\)。那么最后我们可以得到一个关于 \(f_0\) 的一元一次方程 \(f_0=P\cdot f_0+Q\)。(\(P,Q\))都是有转移方程计算出来的。如果最后 \(P=1\),就说明无解,输出 \(-1\) 即可。

//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 n,m,k,b[maxn];

struct dp {
	double p,q;
}f[maxn];

signed main() {
	cin>>n>>m>>k;
	for(int i=1;i<=k;i++) {
		b[read()]=1;
	}
	
	double ps=0,qs=0;
	int tot=1;
	for(int i=n-1;i>=0;i--)  {
		if(i+m+1<=n) {
			ps-=f[i+m+1].p;
			qs-=f[i+m+1].q;
			tot--;
		}
		if(!b[i]) {
			f[i].p=ps/m;
			f[i].q=qs/m+1.000;
		}
		else {
			f[i].p=1.000;
			f[i].q=0.000;
		}
		ps+=f[i].p;
		qs+=f[i].q;
		tot++;
	}
	
	if(fabs(1.00000-f[0].p)<=1e-12) {
		printf("-1\n");
	}
	else
		cout<<fixed<<setprecision(6)<<f[0].q/(1-f[0].p)<<endl;
	return 0;
}

另外还有一个二分的方法,这里就一笔带过略讲一下。

我们可以二分 \(f_0\),然后带回到原式中验证是否正确由于 \(f_0\)满足单调性,我们可以用二分答案来解决这个问题。

当然官方题解中还给了一个优化方法,就是把常用的 \(mid=\frac{l+r}{2}\),改成 \(mid=\sqrt{l\cdot r}\),会更快一些。

posted @ 2021-01-24 20:32  huayucaiji  阅读(246)  评论(1)    收藏  举报