zoukankan      html  css  js  c++  java
  • 【AtCoder】AtCoder Grand Contest 020 解题报告

    点此进入比赛

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

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

    签到题

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

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

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

    代码:(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)人,问可能的初始人数最小值和最大值。
    • (nle10^5)

    倒推

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

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

    则这次操作前可能的人数范围就是([lceilfrac l{a_i} ceil imes a_i,(lfloorfrac r{a_i} floor+1) imes 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
    ",l,r),0;//输出答案
    }
    

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

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

    中位数

    考虑中位数有什么性质?

    假设我们算入空集,则发现在这题中任意一种选法与其补集的和都是(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
    ",i),0;//找到第一个大于等于t/2的数
    }
    

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

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

    最优情况

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

    所以我们假设(Age B)

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

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

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

    代码实现

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

    [1le A-x imes t-(lceilfrac{B-x}t ceil-1)le t ]

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

    [x imes t+lceilfrac{B-x}t ceille A ]

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

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

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

    • (iin[1,a]):如果(imod(t+1) ot=0)则为A,否则为B
    • (iin(a,a+b])A
    • (iin(a+b,a+b+c])B
    • (iin(a+b+c,A+B]):如果(imod(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} imes f_{i,r}\g_{l,r}=sum_{d|(r-l+1)}[d为循环节]f_{l,l+d-1} ]

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

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

    代码:(O(n^5+n^2 imes2^{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
    ",F(s)),0;
    }
    

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

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

    实数离散化

    一个诡异的套路?

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

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

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

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

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

    (DP)

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

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

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

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

    (DP)方程就是:

    [f_{i+1,min{n imes mmax{j,i+a_x imes n},k|2^{x-1}} exttt{+=}f_{i,j,k} ]

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

    代码:(O((n-1)! imes (nm)^2 imes 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
    ",t),0;//概率=合法方案数/总方案数
    }
    
  • 相关阅读:
    单点登录原理与简单实现
    关系型数据库中的关键字、主关键字和候选关键字
    无向图的顶点连通度
    memcmp()直接比较两个数组的大小
    静态字典树
    动态字典树
    poj 1149
    poj 2112 floyd+Dinic最大流+二分最小值
    POJ 1698 (二分图的多重匹配)
    网络流算法
  • 原文地址:https://www.cnblogs.com/chenxiaoran666/p/AtCoderAGC020.html
Copyright © 2011-2022 走看看