A
如果两枚棋子靠在一起,那么肯定是先手必败。
注意到一轮操作后两枚棋子的坐标差的奇偶性不变,于是可以直接判断。
int main()
{
int n=read(),a=read(),b=read();
int d=abs(a-b);
if (d&1) puts("Borys");
else puts("Alice");
return 0;
}
B
考虑倒推,若只考虑(a_{i+1})及其以后的数时的合法答案区间为([L,R]).那么对于(a_i),找到在此区间中且为(a_i)的倍数的最小和最大的数(L',R'),则考虑(a_{i})及其以后的数时的合法答案区间为([a_iL,a_i(R+1)-1]).
int n,a[100100];
int main()
{
n=read();
rep(i,1,n) a[i]=read();
if (a[n]!=2) {puts("-1");return 0;}
ll L,R;L=R=2;
per(i,n,1)
{
L=(L-1)/a[i]+1;R=R/a[i];
if (L>R) {puts("-1");return 0;}
L*=a[i];R=(R+1)*a[i]-1;
if (L>R) {puts("-1");return 0;}
}
printf("%lld %lld
",L,R);
return 0;
}
C
注意到所有的(S_i+S_{2^N-1-i})都是相同的,均为整个序列的和,那么我们可以在只求出所有的可能子集和而不求出方案数的情况下得到中位数:只需要从总和的一半开始往后枚举,找到的第一个数即为中位数。
求出所有可能的子集和可以使用bitset优化背包。
int n;
bitset<4004000> f;
int main()
{
f[0]=1;int s=0;
n=read();
rep(i,1,n)
{
int x=read();
f|=(f<<x);s+=x;
}
rep(i,(s+1)>>1,s)
{
if (f[i]) {printf("%d",i);break;}
}
return 0;
}
D
首先考虑最短的极长连续段的长度,为(max(lceilfrac{a}{b+1} ceil,lceilfrac{b}{a+1} ceil)),证明的话可以考虑(a)个(A)将字符串分成了(a+1)段,之后所有的B平均的插入这些段中即可(交换字母同理)
以下过程中用(A_k)替代字符串中连续的(k)个A.
最naive的想法是答案串就是按照上面的证明过程来构造的,但是我们可以让所有的A出现的尽可能靠前,而在之后不足的部分中使用(B_k)来替代原来(A_{k-1})的位置。形式化的,答案串可以看成是(A_kBA_kBcdots)的前缀和(cdots AB_kAB_k)的后缀。
那么关键就是求出两个串交换的分界点了,我们可以二分这个分界点,注意为了避免出现如(A_kB|B_kA)这种情况需要一些特判。
int a,b,c,d,k;
int chk(int mid)
{
if (!mid) return 1;
if (mid%(k+1)==0) return chk(mid-1);
int rsta=a-(mid-mid/(k+1)),rstb=b-mid/(k+1);
return rstb<=1ll*(rsta+1)*k;
}
int main()
{
int q=read();
while (q--)
{
a=read();b=read();c=read();d=read();
k=(max(a,b)-1)/(min(a,b)+1)+1;
int l=0,r=a+b,pos=0;
while (l<=r)
{
int mid=(l+r)>>1;
if (chk(mid)) {pos=mid;l=mid+1;}
else r=mid-1;
}
rep(i,c,min(pos,d))
{
if (i%(k+1)) putchar('A');
else putchar('B');
}
rep(i,max(c,pos+1),d)
{
int tmp=a+b-i+1;
if (tmp%(k+1)) putchar('B');
else putchar('A');
}
puts("");
}
return 0;
}
E
梦想型动态规划?(雾)
考虑对于一个固定的串如何求其压缩方案数,这是一个经典的区间dp问题,记(f_{l,r})为将区间([l,r])压缩的方案数,(g_{l,r})为将区间([l,r])压缩成单位区间的方案数,这里的单位区间指的是单个字符或一个由括号括起来的字符串(允许嵌套),转移有:
其中(mathrm{valid}(l,l+d-1))定义为所选取的子区间的串是否是([l,r])的周期串。
现在考虑原问题,可以使用类似的动态规划解决,将状态直接使用字符串定义,同时注意g的转移中需要枚举划分后对每个子串取并而进行转移。
map<string,int> f,g;
int calcf(string);int calcg(string);
int calcg(string s)
{
if (g[s]) return g[s];
int n=s.size(),ans=0;
rep(d,1,n-1)
{
if (n%d) continue;
string t="";
rep(i,0,d-1)
{
int ch=1;
for (int j=i;j<n;j+=d) ch&=(s[j]-'0');
t=t+(char)(ch+'0');
}
ans=(ans+calcf(t))%maxd;
}
g[s]=ans;return ans;
}
int calcf(string s)
{
if (f[s]) return f[s];
int n=s.size(),ans=0;
rep(i,1,n)
ans=(ans+1ll*calcg(s.substr(0,i))*calcf(s.substr(i,n))%maxd)%maxd;
f[s]=ans;return ans;
}
int main()
{
string s;
cin >> s;
f[""]=1;
g[""]=1;g["0"]=1;g["1"]=2;
printf("%d",calcf(s));
return 0;
}
F
nb题,原本还以为是什么积分理论,最后居然能转成离散问题。
首先考虑断环为链,以最长弧的一段为端点断开这个环。
接下来是一个很重要的转化:由于所有的弧长都是整数,所以我们不关心所有弧起始点的小数部分,我们只关心它们之间的大小关系。由于这个(N)很小,我们可以暴力枚举这个大小关系。
问题被转化为:在区间([0,NC))上,有一条已知长度的从(0)开始的线段覆盖这个区间,还有(N-1)个长度已知,且在区间上的起点(mod N)已知的线段。要使得这些线段能完全覆盖这个区间的方案数。这个可以使用状压dp处理,记(f_{i,j,S})为当前在点(i),已经覆盖到点(j),使用的线段集合为(S)的方案数,转移的话考虑(i+1)是否是某个线段的起点即可。
这时候还有一个问题没有解决:那些因为断开了环而断开的线段如何处理?由于我们选择的是最长线段的端点为起点,所以在区间起始处的线段不会对这个区间的覆盖产生贡献,否则就会与”最长”相矛盾。
int n,m,l[7],id[7],f[310][310][130];
int main()
{
n=read();m=read();
rep(i,1,n) l[i]=read();
sort(l+1,l+1+n);
rep(i,1,n-1) id[i]=i;
int lim=(1<<(n-1))-1;
double ans=0.0;
while (1)
{
memset(f,0,sizeof(f));
f[0][l[n]*n][0]=1;
rep(i,1,n*m-1)
{
rep(j,i,n*m)
{
rep(sta,0,lim)
{
f[i][j][sta]+=f[i-1][j][sta];
int x=i%n;
if ((!x) || ((sta>>(x-1))&1)) continue;
int nxtj=min(n*m,max(j,i+l[id[x]]*n)),nxts=(sta|(1<<(x-1)));
f[i][nxtj][nxts]+=f[i-1][j][sta];
}
}
}
ans+=f[n*m-1][n*m][lim];
if (!next_permutation(id+1,id+n)) break;
}
rep(i,1,n-1) ans/=i;
rep(i,1,n-1) ans/=m;
printf("%0.15lf",ans);
return 0;
}