AtCoder Regular Contest 110
下午的 AtCoder 时好时坏 晚上的 AtCoder 赛活神仙
A - Redundant Redundancy
给定一个整数 \(N(2\leq N\leq 30)\) ,求一个整数 \(x(N\leq x\leq 10^{13})\) 使得对于任意的整数 \(y(2\leq y\leq N)\) ,\(x\bmod y=1\) .
Thoughts & Solution
签到题。 一个可行解是:
//Author: RingweEH
ll gcd( ll a,ll b )
{
return (b==0) ? a : gcd( b,a%b );
}
int main()
{
ll n=read(),x=1;
for ( ll i=2; i<=n; i++ )
{
ll gc=gcd( x,i ); x=x*i/gc;
}
printf( "%lld\n",x+1 );
}
B - Many 110
令 \(S\) 为 110
重复 \(10^{10}\) 的结果。给定一个长度为 \(N\) 的字符串 \(T\) ,求其出现次数。
Thoughts & Solution
小型分讨。
- 对于 \(n\leq 2\) ,直接判断输出
- 对于 \(n>2\) ,根据 \(T\) 的前三个字符判断是从 \(S\) 串的 1/2/3 位开始,然后构造一个能恰好包含 \(T\) 的(长度)的
110
串 \(str\) ,暴力匹配;如果可以,那么设 \(str\) 中使用了 \(x\) 个110
,答案就是 \(10^{10}-(x-1)\) .否则答案为 \(0\) .如果根据前三位无法构造也是 \(0\) . - 具体见代码.
//Author: RingweEH
const int N=2e5+10;
int n;
char s[N],str[N+10];
int main()
{
n=read(); scanf( "%s",s+1 );
ll cnt=n/3+2;
for ( int i=1; i<=cnt*3; i+=3 )
{
str[i]='1'; str[i+1]='1'; str[i+2]='0';
}
if ( n<=2 )
{
if ( n==1 )
{
if ( s[1]=='0' ) printf( "10000000000\n" );
else printf( "20000000000\n" );
return 0;
}
if ( (s[1]=='1') && (s[2]=='1') ) printf( "10000000000\n" );
if ( (s[1]=='1') && (s[2]=='0') ) printf( "10000000000\n" );
if ( (s[1]=='0') && (s[2]=='0') ) printf( "0\n" );
if ( (s[1]=='0') && (s[2]=='1') ) printf( "9999999999\n" );
return 0;
}
if ( (s[1]=='1') && (s[2]=='1') && (s[3]=='0') )
{
for ( int i=1; i<=n; i++ )
if ( s[i]!=str[i] ) { printf( "0\n" ); return 0; }
if ( n%3 ) cnt=n/3+1;
else cnt=n/3;
printf( "%lld\n",10000000000-(cnt-1) ); return 0;
}
else if ( (s[1]=='1') && (s[2]=='0') && (s[3]=='1') )
{
for ( int i=2; i<=n+1; i++ )
if ( str[i]!=s[i-1] ) { printf( "0\n" ); return 0; }
cnt=((n+1)%3) ? (n+1)/3+1 : (n+1)/3;
printf( "%lld\n",10000000000-(cnt-1) ); return 0;
}
else if ( (s[1]=='0') && (s[2]=='1') && (s[3]=='1') )
{
for ( int i=3; i<=n+2; i++ )
if ( str[i]!=s[i-2] ) { printf( "0\n" ); return 0; }
cnt=((n+2)%3) ? (n+2)/3+1 : (n+2)/3;
printf( "%lld\n",10000000000-(cnt-1) ); return 0;
}
else { printf( "0\n" ); return 0; }
}
C - Exoswap
给定一个长为 \(N\) 的排列 \(P=P_1,P_2,\dots ,P_N\) .
你需要以任意顺序做以下 \(N-1\) 个操作,每个 恰好一次 :
- 交换 \(P_i,P_{i+1}(i=1\sim n-1)\) .
问是否能将 \(P\) 升序排列,如果不能输出 -1 ,如果能给出方案。
Thoughts & Solution
考场的奇怪思路 AC 了……
这题不建议看这篇题解,是考场乱搞的做法(不过做法是对的,就是代码思路比较奇怪)
- 交换两个相邻的元素,逆序对增减量为 1 。那么如果总逆序对个数多于 \(n\) 个,显然 Impossible.
- 如果有三个连续元素递减,那么显然 Impossible.
- 对于所有连续逆序对 \(P_i>P_{i+1}\) ,将 \(i\) 放入队列中,依次处理:
- 每次取出队首,如果仍然为逆序对,那么交换,\(cnt++\) ;
- 判断前一个和后一个,如果出现逆序对,入队;
- 最后判断是否恰好交换 \(n-1\) 次,和是否所有元素都归位。
//Author: RingweEH
#define lowbit(x) ((x)&(-x))
#define PII pair<int,int>
const int N=2e5+10;
int n,p[N],c[N],ans[N];
queue<int> q;
void add( int x,int val ) { for ( ; x<=n; x+=lowbit(x) ) c[x]++; }
int get_sum( int x ) { int res=0; for ( ; x; x-=lowbit(x) ) res+=c[x]; return res; }
int main()
{
n=read();
for ( int i=1; i<=n; i++ )
p[i]=read();
int cnt=0;
for ( int i=n; i>=1; i-- )
{
cnt+=get_sum( p[i]-1 );
if ( cnt>n ) { printf( "-1" ); return 0; }
add( p[i],1 );
}
if ( cnt>n ) { printf( "-1" ); return 0; }
for ( int i=1; i<n-1; i++ )
if ( (p[i]>p[i+1]) && (p[i+1]>p[i+2]) ) { printf( "-1" ); return 0; }
for ( int i=1; i<n; i++ )
if ( (p[i]>p[i+1]) ) q.push( i );
cnt=0;
while ( !q.empty() )
{
int u=q.front(); q.pop();
if ( p[u]>p[u+1] ) swap( p[u],p[u+1] ),ans[++cnt]=u;
if ( (u>1) && (p[u-1]>p[u]) ) q.push( u-1 );
if ( (u<n-1) && (p[u+1]>p[u+2]) ) q.push( u+1 );
}
if ( cnt!=(n-1) ) { printf( "-1" ); return 0; }
for ( int i=1; i<=n; i++ )
if ( p[i]!=i ) { printf( "-1" ); return 0; }
for ( int i=1; i<n; i++ )
printf( "%d\n",ans[i] );
}
D - Binomial Coefficient is Fun
给定一个长为 \(n\) 的非负整数序列 \(A\) 。
对于所有满足 \(\sum_i B_i\leq M,B_i\in N\) 的序列 \(B\) ,求 $$\prod_{i=1}^n {B_i\choose A_i}$$ 的和对 \(1e9+7\) 取模的结果。
\(1\leq N\leq 2000,1\leq M\leq 1e9,0\leq A_i\leq 2000\) .
Thoughts & Solution
常见套路:有子问题 \(\sum B_i=M\) .
由于所求是乘积,那么就只有 \(B_i\ge A_i\) 时才会有贡献。
考虑这个问题的组合意义,将 \(B\) 看做 \(M\) 个小球。由于 \(B_i\) 的值可以改变,考虑加入 \(N-1\) 个隔板来分隔 \(B_i,B_{i+1}\) .
然后来放置 \(A\) 。灵活运用 \(B\) 可变这一性质,可以通过 \(A\) 来确定隔板位置,即:
- 总共选出 \(\sum A_i+N-1\) 个位置;
- \(1\sim A_1\) 表示 \(A_1\) ,\(A_1+1\) 是第一个隔板,后面以此类推。
- \(B_i\) 的值随着隔板位置变化,且一定合法。
问题转化为 \(M+N-1\) 个位置中选择 \(\sum A_i+N-1\) 个,子问题答案为:
然后来看最终答案。
//Author: RingweEH
const ll Mod=1e9+7;
ll n,m;
ll power( ll a,ll b )
{
ll res=1;
for ( ; b; b>>=1,a=a*a%Mod )
if ( b&1 ) res=res*a%Mod;
return res;
}
int main()
{
n=read(); m=read(); int sum=0;
for ( int i=1; i<=n; i++ )
sum=sum+read();
if ( m<sum ) { printf( "0\n" ); return 0; }
m+=n; sum+=n;
ll ans=1;
for ( ll i=1; i<=sum; i++ )
ans=ans*power( i,Mod-2 )%Mod*(m-i+1)%Mod;
printf( "%lld\n",ans );
}
E - Shorten ABC
给定一个只包含 ABC
的字符串 \(s\) ,每次可以选择相邻的两个不同的字符,合成与他们不同的字符。
问任意次操作(可以为 0 )后能变成多少不同的字符串,对 \(1e9+7\) 取模。\(|S|\leq 1e6\) .
Thoughts & Solution
设 A
,B
, C
分别为 \(1,2,3\) ,如果不考虑相邻的不相同的限制,那么这样的“合成”就是将 \(x,y\) 替换为 \(x\oplus y\) .
对于 \(x\neq y\) 的限制,归纳可证,等价于区间异或和不为 \(0\) 且区间内字符不完全相等或区间长度为 \(1\) .
这样,题目的要求就是,将原序列划分成若干个区间求异或值。
为了保证方案不算重,强制除了第一个区间,任意区间的任意非空前缀异或和不为 \(0\) ,否则可以将该前缀加入上一个区间。
设 \(f[i]\) 表示划分前 \(i\) 个的方案数。转移方程就是:
令 \(nxt[i]\) 表示 \(i+1\sim n\) 中最早出现的 \(j\) ,使得 \(suma[i]=suma[j]\) ,其中 \(suma\) 表示异或前缀和。
这部分可以用差分维护。
//Author: RingweEH
const int N=1e6+10;
const ll Mod=1e9+7;
int n,a[N],las[5],nxt[N];
ll f[N];
char s[N];
void add( int l,int r,ll val )
{
f[l]=(f[l]+val)%Mod;
if ( r+1<=n ) f[r+1]=(f[r+1]+Mod-val)%Mod;
}
int main()
{
n=read(); scanf( "%s",s+1 );
a[0]=0; bool fl=0;
for ( int i=1; i<=n; i++ )
{
if ( s[i]=='A' ) a[i]=1;
else if ( s[i]=='B' ) a[i]=2;
else a[i]=3;
if ( (a[i]^a[i-1]) && (i>1) ) fl=1;
}
if ( !fl ) { printf( "1\n" ); return 0; } //判断全部相等
for ( int i=1; i<=n; i++ )
a[i]^=a[i-1];
for ( int i=0; i<5; i++ )
las[i]=n+1;
for ( int i=n; i>=1; i-- )
nxt[i]=las[a[i]],las[a[i]]=i;
for ( int i=1; i<=n; i++ )
if ( a[i] ) add( i,i,1 );
for ( int i=1; i<=n; i++ )
{
if ( i>1 ) f[i]=(f[i]+f[i-1])%Mod;
add( i+1,nxt[i]-1,f[i] );
}
printf( "%lld\n",f[n] );
return 0;
}
F - Esoswap
给定一个长为 \(N(2\leq N\leq 100)\) 的排列 \(P=P_0,\dots,P_N-1\) .你可以进行以下操作最多 \(2e5\) 次:
- 选择一个数 \(i(0\leq i\leq N-1)\) ,交换 \(P_i,P(i+P_i)\bmod N\)
如果最终不能成为升序,输出 -1
,否则给出方案。
Thoughts & Solution
先来观察一下样例。
8
7 1 2 6 4 0 5 3
发现有一个很简单的操作方式:无脑操作 \(0\) 这个位置。
7 1 2 6 4 0 5 3
3 1 2 6 4 0 5 7
6 1 2 3 4 0 5 7
5 1 2 3 4 0 6 7
0 1 2 3 4 5 6 7
考虑对 \(0\) 这个位置,有:
- 每个在这里的数都会被归位到 \(P_i\) 即最终位置
- 这样的变换不会重复(显然,因为每次到这里的 \(P\) 不可能一样)
- 变换到 \(0\) 归位之后终止
进一步的,发现对于所有位置,除了第一条以外都成立。
然后,考虑进行如下构造:
-
从 \(n-1\sim 1\) 倒序操作使得 \(p[i]=n-1-i\) 。
Proof
当你操作 \(i\) 位置时,已经固定了 \(i+1\sim n-1\) , \([0,n-i-1]\) 这些数都已经固定下来,因此操作不会破坏已固定的数的顺序。
由于上面的性质,变换不会重复,因此这部分操作数至多 \(n^2\) .
-
现在所有数都已经降序排列了。考虑通过 \(1\) 来调换。
-
设已经排成了 “倒序+\(0\ 1\) +顺序”的形式。那么,我们可以通过移动 \(1\) 到末尾,然后再和前面的调换插入下一个数。
-
Example:
7 6 5 4 0 1 2 3 => 7 6 5 4 0 2 1 3 => 7 6 5 4 0 2 3 1 (调换1) 7 6 5 1 0 2 3 4 => 7 6 5 0 1 2 3 4 (插入)
然后就做完了。所以无解是啥啊
//Author: RingweEH
const int N=110;
int n,p[N];
vector<int> ans;
void Operation( int x )
{
ans.push_back( x );
swap( p[x],p[(x+p[x])%n] );
}
int main()
{
n=read();
for ( int i=0; i<n; i++ )
p[i]=read();
for ( int i=n-1; i; i-- )
while ( p[i]^(n-i-1) ) Operation( i );
for ( int i=n-2; i>=0; i-- )
{
for ( int j=i+1; j<n-1; j++ )
Operation( j );
Operation( i ); Operation( i );
}
printf( "%d\n",ans.size() );
for ( int i=0 ;i<ans.size(); i++ )
printf( "%d\n",ans[i] );
return 0;
}