把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

【AtCoder】AtCoder Grand Contest 020 解题报告

点此进入比赛

\(A\):Move and Win(点此看题面

  • 一张\(n\)个格子的纸条,初始对弈双方棋子分别在\(A,B\)
  • 轮到一人操作时,他可以将棋子向左或向右移到一个空格,若不能操作就输了。
  • 求谁能赢。
  • \(1\le A<B\le n\le100\)

签到题

考虑不可能两个人都向两边走,而一人向内一人向外时二者距离不变。

因此只需考虑两者都向中间走,那么假设甲碰到了乙,则乙就只能不断往边上走,必输。

那么只要判下两者间距离奇偶性就好了。

代码:\(O(1)\)

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
using namespace std;
int n,A,B;
int main()
{
	return scanf("%d%d%d",&n,&A,&B),puts((A^B)&1?"Borys":"Alice"),0;//判下距离奇偶性
}

\(B\):Ice Rink Game(点此看题面

  • 初始有若干人,第\(i\)轮游戏给出一个\(a_i\),要求当前人数减小为最大的\(a_i\)倍数。
  • \(n\)轮之后只剩\(2\)人,问可能的初始人数最小值和最大值。
  • \(n\le10^5\)

倒推

这种题目显然倒着推一遍就解决了。。。

设当前可能人数范围为\([l,r]\),该次操作的数为\(a_i\)

则这次操作前可能的人数范围就是\([\lceil\frac l{a_i}\rceil\times a_i,(\lfloor\frac r{a_i}\rfloor+1)\times a_i-1]\)

代码:\(O(n)\)

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 100000
#define LL long long
using namespace std;
int n,a[N+5];
int main()
{
	RI i;LL l=2,r=2;for(scanf("%d",&n),i=1;i<=n;++i) scanf("%d",a+i);//初始人数为2
	for(i=n;i;--i) if((l=((l-1)/a[i]+1)*a[i])>(r=(r/a[i]+1)*a[i]-1)) return puts("-1"),0;//倒推
	return printf("%lld %lld\n",l,r),0;//输出答案
}

\(C\):Median Sum(点此看题面

  • 给定一个长度为\(n\)的序列\(A\),求所有子序列和的中位数。
  • \(n,A_i\le2000\)

中位数

考虑中位数有什么性质?

假设我们算入空集,则发现在这题中任意一种选法与其补集的和都是\(\sum_{i=1}^nA_i\)

因此算入空集的中位数就是\(\frac{\sum_{i=1}^nA_i}2\)

那么少了空集之后的中位数自然就是大于等于\(\frac{\sum_{i=1}^nA_i}2\)的第一个数了。

\(bitset\)优化\(01\)背包

这种问题显然是一个\(01\)背包问题,然而原本的问题并不好优化。

现在相当于只需要判一个数能否得到,直接\(bitset\)优化就好了。

代码:\(O(\frac{n^3}{32})\)

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 2000
using namespace std;
int n;bitset<N*N+5> s;
int main()
{
	RI i,x,t=0;for(s.set(0),scanf("%d",&n),i=1;i<=n;++i) scanf("%d",&x),t+=x,s|=s<<x;//bitset优化01背包
	for(i=t+1>>1;i<=n*N;++i) if(s.test(i)) return printf("%d\n",i),0;//找到第一个大于等于t/2的数
}

\(D\):Min Max Repetition(点此看题面

  • 给定\(A,B\),求一个\(A\)A\(B\)B组成的字符串,满足:
    • 连续相同字符个数的最大值最小。
    • 在此基础上,字典序最小。
  • 输出该字符串\(C\sim D\)位。
  • 数据组数\(\le10^3,A,B\le5\times10^8,D-C+1\le100\)

最优情况

\(A<B\),只要交换\(A,B\),将得到的字符串翻转并反转\(A,B\)就可以转化成\(A>B\)的情况。

所以我们假设\(A\ge B\)

先求出连续相同字符个数的最大值的最小值,显然将\(B\)尽可能均匀地插入\(A\)中肯定能得到最小值,所以\(t=\lceil\frac{A}{B+1}\rceil\)

然后经过一定的推导发现最优情况应如下所示:

其中若干指的是个数在\([1,t]\)中。

代码实现

推一推\(x\)的取值范围:

\[1\le A-x\times t-(\lceil\frac{B-x}t\rceil-1)\le t \]

因为我们要最大化\(x\),所以只需考虑式子的左半边,移项得到:

\[x\times t+\lceil\frac{B-x}t\rceil\le A \]

显然这个东西没法直接推导,于是我们考虑二分出\(x\)的值。

考虑最优情况可以划分为四部分,于是我们求出前三段长度\(a=x\times(t+1)\)\(b=A-x\times t-(\lceil\frac{B-x}t\rceil-1)\)\(c=(B-x-1)\mod t+1\)

求第\(i\)位的值时根据它落在序列中的哪一块分类讨论:

  • \(i\in[1,a]\):如果\(i\mod(t+1)\not=0\)则为A,否则为B
  • \(i\in(a,a+b]\)A
  • \(i\in(a+b,a+b+c]\)B
  • \(i\in(a+b+c,A+B]\):如果\(i\mod(t+1)=1\)则为A,否则为B

代码:\(O(100T)\)

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
using namespace std;
int A,B,C,D,t,x,a,b,c;char s[105];
I int Find(CI A,CI B,CI t)//二分出x的值
{
	RI l=0,r=A,mid;W(l<r) mid=l+r+1>>1,1LL*t*mid+(B-mid-1)/t+1<=A?l=mid:r=mid-1;return l;
}
I void Work(CI A,CI B,CI C,CI D)//求解
{
	t=(A-1)/(B+1)+1,x=Find(A,B,t),a=x*(t+1),b=A-x*t-(B-x-1)/t,c=(B-x-1)%t+1;//求出三个关键点
	for(RI i=C;i<=D;++i) s[i-C+1]=i<=a?(i%(t+1)?'A':'B'):(i<=a+b?'A':(i<=a+b+c?'B':((i-a-b-c)%(t+1)==1?'A':'B')));//根据落在哪一块分类讨论
}
int main()
{
	RI Tt,i,j;scanf("%d",&Tt);W(Tt--)
	{
		if(scanf("%d%d%d%d",&A,&B,&C,&D),A>=B) Work(A,B,C,D);//如果A≥B直接做
		else for(Work(B,A,A+B-D+1,A+B-C+1),i=1;i<=D-C+2-i;++i)//如果A<B转化为A>B
			s[i]=131-s[i],i^(D-C+2-i)&&(s[D-C+2-i]=131-s[D-C+2-i]),swap(s[i],s[D-C+2-i]);//翻转并反转A,B
		s[D-C+2]=0,puts(s+1);//输出答案
	}return 0;
}

\(E\):Encoding Subsets(点此看题面

  • 对于一个\(01\)串,你可以把\(n\)个连续相同串\(A\)压缩成一个(Axn)(算作一个字符)。
  • 给定\(01\)\(S\),求\(S\)所有子集压缩方案数之和(这里的子集指二进制数\(S\)的子集)。
  • \(|S|\le100\)

暴搜

假设在字符串固定的情况下,我们设\(f_{l,r}\)表示区间\([l,r]\)的压缩方案数,\(g_{l,r}\)表示区间\([l,r]\)压缩为一个字符的方案数,得到\(DP\)

\[f_{l,r}=\sum_{i=l+1}^r g_{l,i-1}\times f_{i,r}\\g_{l,r}=\sum_{d|(r-l+1)}[d为循环节]f_{l,l+d-1} \]

然后考虑字符串不固定,其实更加简单了。

因为任意循环节都是有可能的,所以\(DP\)\(g\)的时候我们只要求出每一段的并值即可。

代码:\(O(n^5+n^2\times2^{\frac n8})\)

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 100
#define X 998244353
using namespace std;
string s;map<string,int> f,g;
I int F(Con string& s);
I int G(Con string& s)//DP求G
{
	if(s=="") return 1;if(s=="0") return 1;if(s=="1") return 2;if(g.count(s)) return g[s];//判边界
	RI i,j,k,x,l=s.length(),t=0;for(i=1;i^l;++i) if(!(l%i))//枚举循环节
		{string ns="";for(j=0;j^i;++j) {for(x=49,k=j;k<l;k+=i) x&=s[k];ns+=x;}t=(t+F(ns))%X;}//求出每一段的并值继续DP
	return g[s]=t;//记忆化
}
I int F(Con string& s)//DP求F
{
	if(s=="") return 1;if(f.count(s)) return f[s];//判边界
	RI i,l=s.length(),t=0;for(i=1;i<=l;++i) t=(1LL*G(s.substr(0,i))*F(s.substr(i,l))+t)%X;//将串断成两部分
	return f[s]=t;//记忆化
}
int main()
{
	return cin>>s,printf("%d\n",F(s)),0;
}

\(F\):Arcs on a Circle(点此看题面

  • \(n\)条长度为\(a_{1\sim n}\)的线段和一个周长为\(m\)的圆。
  • 随机将这\(n\)条线段放到圆上(可以有重叠),问圆上所有点都被覆盖的概率。
  • \(n\le6,m\le50\)

实数离散化

一个诡异的套路?

考虑线段的长度都是整数,而在比大小时整数部分是很好比较的,关键是小数部分。

然后我们发现对于小数部分我们只需要知道其相对大小,而不需要知道其具体值。

于是,我们对于小数部分离散化。

具体实现时其实是全排列暴枚小数部分的相对大小。

这样一来一共就只有\(n\times m\)个点了。

\(DP\)

接下来的\(DP\)应该是很简单的。

首先我们把最长的线段左端点作为原点,这样就可以化圆为链了。

然后设\(f_{i,j,k}\)为处理完左端点小于等于\(i\)的线段,最大右端点为\(j\),已用线段状压为\(k\)的方案数。

由于我们对小数部分离散化过了,所以每一个左端点只可能有某一条线段。

\(DP\)方程就是:

\[f_{i+1,\min\{n\times m,\max\{j,i+a_x\times n\}\},k|2^{x-1}}\texttt{+=}f_{i,j,k} \]

具体实现中\(i\)这一维完全可以直接删去。

代码:\(O((n-1)!\times (nm)^2\times 2^{n-1})\)

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 6
#define M 50
using namespace std;
int n,m,a[N+5];long long f[N*M+5][1<<N];
int main()
{
	RI i;for(scanf("%d%d",&n,&m),i=1;i<=n;++i) scanf("%d",a+i);
	RI j,k,x,l=1<<n-1,res=0;long long ans=0;sort(a+1,a+n+1);do
	{
		for(j=1;j<=n*m;++j) for(k=0;k^l;++k) f[j][k]=0;//清空
		for(f[n*a[n]][0]=i=1;i<=n*m;++i) if(x=i%n) for(j=i;j<=n*m;++j)//每个左端点只会对应一条线段
			for(k=0;k^l;++k) !(k>>x-1&1)&&(f[min(n*m,max(j,i+n*a[x]))][k|(1<<x-1)]+=f[j][k]);//转移
		++res,ans+=f[n*m][l-1];
	}while(next_permutation(a+1,a+n));//暴枚实数部分相对大小
	long double t=1.0*ans/res;for(i=1;i^n;++i) t/=m;return printf("%.15Lf\n",t),0;//概率=合法方案数/总方案数
}
posted @ 2020-11-02 07:38  TheLostWeak  阅读(135)  评论(0编辑  收藏  举报