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;//概率=合法方案数/总方案数
    }
    
  • 相关阅读:
    UVa 1451 Average (斜率优化)
    POJ 1160 Post Office (四边形不等式优化DP)
    HDU 3507 Print Article (斜率DP)
    LightOJ 1427 Substring Frequency (II) (AC自动机)
    UVa 10245 The Closest Pair Problem (分治)
    POJ 1741 Tree (树分治)
    HDU 3487 Play with Chain (Splay)
    POJ 2828 Buy Tickets (线段树)
    HDU 3723 Delta Wave (高精度+calelan数)
    UVa 1625 Color Length (DP)
  • 原文地址:https://www.cnblogs.com/chenxiaoran666/p/AtCoderAGC020.html
Copyright © 2011-2022 走看看