[总结] Codeforces Round #724 (Div. 2)
[总结] Codeforces Round #724 (Div. 2)
A. Omkar and Bad Story
本题有两种解法。
- 暴力模拟,借用 \(queue\)。
代码为题解代码。@Jr_zLiwen
#include<bits/stdc++.h>
#define rep(a,b,c) for(int c=(a);c<=(b);++c)
#define Clear(a) memset(a,0,sizeof(a))
using namespace std;
inline int read()
{
int res=0;char ch=getchar();bool flag=0;
while(ch<'0'||ch>'9')flag=(ch=='-'),ch=getchar();
while(ch<='9'&&ch>='0')res=res*10+(ch^48),ch=getchar();
return flag ? -res : res;
}
map<int,bool> t;
const int N=105;
int a[N*5],dd[N*5+4],p;int que[100005],H=1,T,qq;
inline void Solve()
{
Clear(a);H=1;qq=T=0;Clear(dd);t.erase(t.begin(),t.end());
int n=read();rep(1,n,i)t[(a[i]=read())]=1;p=n;//rep(1,n,i)printf("%d ",a[i]);
rep(1,n,i)rep(1,n,j)if(i!=j)if(!t[abs(a[i]-a[j])]){t[(que[++T]=abs(a[i]-a[j]))]=1;}
while(H<=T&&p<=300)
{
a[++p]=que[H++];//rep(H,T,j)printf("%d ",que[j]);puts("");
rep(1,p-1,i)if(!t[abs(a[p]-a[i])])que[++T]=abs(a[p]-a[i]),t[abs(a[p]-a[i])]=1;
}
if(H>T){puts("Yes");printf("%d\n",p);rep(1,p,i)printf("%d ",a[i]);puts("");return;}puts("No");
}
int main(){int T=read();while(T--)Solve();}
- 如果有负数就直接输出 \(No\),否则输出 \([0,100]\) 所有数。
代码来自@一九年
#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
#define N 101000
using namespace std;
int n,a[N],T,flag,k,b[N],flagg;
int main(){
scanf("%d",&T);
while(T--){
scanf("%d",&n);
flag=0;k=0;flagg=0;
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
if(a[i]<0) flag=1;
if(a[i]==0) flagg=1;
}
sort(a+1,a+n+1);
if(flag==1){
cout<<"NO"<<endl;
continue;
}
else{
cout<<"YES"<<endl;
cout<<101<<endl;
for(int i=0;i<=100;i++) cout<<i<<" ";
cout<<endl;
}
}
return 0;
}
B. Prinzessin der Verurteilung
只需要暴力往外扩展就行,类似于 \(BFS\) 求边权为 \(1\) 的最短路的过程,保证先扩展到的满足条件的字符串一定是答案。
这里声明一个用法:
if(a.find(s)==string::npos)
这并不是 \(C++11\) 的东西,可以检查一个字符串是否在另一个字符串内,返回指针。
\(string\) 的东西要用 \(string\) 库函数来解决。
比如往 \(string\) 后插东西,在直接引用位置的情况下需要 \(resize\)。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
#include <string>
template <typename T>
inline T read(){
T x=0;char ch=getchar();bool fl=false;
while(!isdigit(ch)){if(ch=='-')fl=true;ch=getchar();}
while(isdigit(ch)){
x=(x<<3)+(x<<1)+(ch^48);ch=getchar();
}
return fl?-x:x;
}
#include <queue>
#define Pair pair<string,int>
#define read() read<int>()
#define mp make_pair
int n;
string a,b;
void solve(){
queue <Pair> q;
q.push(mp(b,0));
while(1){
Pair p=q.front();q.pop();
string s=p.first;int pos=p.second;
s.resize(pos+1);
for(int i=0;i<26;i++){
s[pos]=(char)('a'+i);
//cerr<<s[pos]<<endl;//
if(a.find(s)==string::npos){
cout<<s;puts("");return ;
}
q.push(mp(s,pos+1));
}
}
}
int main(){
int T=read();
while(T--){
cin>>n>>a;
solve();
}
}
代码参考@Acc_Robin 。
C. Diluc and Kaeya
应该说是
唯一独立想出来的题了。
可以参考我的题解。
题解
- 何为无解情况?
显然,当 \(n(D)\) 与 \(n(K)\) 互质时无解,因为此时第一次出现 \(n(D):n(K)\) 这个比例,它肯定是无法划分的。
- 如何统计答案?
由于第一次出现比例 \(c\) 时是无解情况,第二次出现时一定存在一种可划分方案把 \(S\) 划分为比例相同的两部分。
一边扫描一边用 \(map\) 统计即可,复杂度 \(\text O(n\ logn)\)。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
#include <string>
template <typename T>
inline T read(){
T x=0;char ch=getchar();bool fl=false;
while(!isdigit(ch)){if(ch=='-')fl=true;ch=getchar();}
while(isdigit(ch)){
x=(x<<3)+(x<<1)+(ch^48);ch=getchar();
}
return fl?-x:x;
}
#include <map>
#define Pair pair<int,int>
const int maxn = 5e5 + 10;
int ans[maxn],top=0;
#define read() read<int>()
#define mp make_pair
int n;
char ch[maxn];
int gcd(int a,int b){
if(!b)return a;
return gcd(b,a%b);
}
void solve(){
memset(ans,0,sizeof ans);
map <Pair,int> m;
int D=0,K=0;
for(int i=1;i<=n;i++){
D+= ch[i]=='D';K+= ch[i]=='K';
int g=gcd(D,K);
//cerr<<D<<" "<<K<<" "<<g<<endl;//
ans[i]=++m[mp(D/g,K/g)];
}
for(int i=1;i<=n;i++)printf("%d ",ans[i]);
puts("");
}
int main(){
int T=read();
while(T--){
n=read();cin>>(ch+1);
solve();
}
}
D. Omkar and Medians
考虑到中位数一个非常重要的性质:向原序列加入两个数之后中位数最多移动 1 位。
所以用 \(multiset\) 维护前驱后继(你写平衡树也没人拦着你)。
假设现在在 \(b_{i+1}\) 这个数,原序列 \([a_{1},a_{2i-1}]\) 的中位数是 \(b_i\),当前序列在此数有没有解 当且仅当当前数是\([b_1,b_i]\) 的前驱、后继或者在 \([b_1,b_i]\) 中出现过。
思路来自@neal。
强调一个东西:
*prev(it);//it是迭代器
这是 \(C++11\) 里的东西,可以返回前驱。
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
template <typename T>
inline T read(){
T x=0;char ch=getchar();bool fl=false;
while(!isdigit(ch)){
if(ch=='-')fl=true;ch=getchar();
}
while(isdigit(ch)){
x=(x<<3)+(x<<1)+(ch^48);ch=getchar();
}
return fl?-x:x;
}
#include <set>
#define read() read<int>()
const int maxn = 2e5 + 10;
int a[maxn],n,la;
multiset <int> s;
bool check(int x){
if(!s.size())return true;
for(int i=0;i<2;i++){
multiset<int> :: iterator it=(i==0)?s.lower_bound(x):s.upper_bound(x);
if(it!=s.end() && *it==la)return true;
if(it!=s.begin() && *prev(it)==la)return true;
}
return false;
}
void solve(){
s.clear();la=0;
for(int i=1;i<=n;i++){
if(!check(a[i])){
puts("NO");return ;
}
la=a[i];s.insert(a[i]);
}
puts("YES");
}
int main(){
int T=read();
while(T--){
n=read();
for(int i=1;i<=n;i++)a[i]=read();
solve();
}
}
E. Omkar and Forest
题意
给你一个 \(n\times m\) 的网络,其中 \(0\) 代表数字,'#' 可以填任意一个非负整数。
问你有多少种填的方案。
其中需要满足:
- 任意相邻两个点之间差的绝对值不超过 \(1\)。
- 对于 '#' 点来说,该点必须严格大于至少一个相邻的点。
题解
考虑本题一个比较难想的性质:
- 当图中的 '#' 一部分确定为 \(0\) 时,此时图的解是唯一的。
这是比较难推的,但是貌似举不出反例来。
比如当前点为正权值的 '#',周围的权值集合为 \(S\)。
- \(S=1\),权值确定。
- \(S\not=1\),由于第一条性质,'#' 的权值也是唯一的。
所以合理外推,不难得到对于确定的 '#' 为 \(0\) 的布局,方案是一定的。
\(ans=2^{cnt}\),其中 \(cnt\) 为 \(0\) 的个数。
因为当布局中 \(0\) 不存在时,不存在合法方案,因此如果网络全是 '#' 答案需要 \(ans--\)。
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
template <typename T>
inline T read(){
T x=0;char ch=getchar();bool fl=false;
while(!isdigit(ch)){
if(ch=='-')fl=true;ch=getchar();
}
while(isdigit(ch)){
x=(x<<3)+(x<<1)+(ch^48);ch=getchar();
}
return fl?-x:x;
}
const int maxn = 2000 + 10;
#define read() read<int>()
#define LL long long
const LL P = 1e9 + 7;
char ch[maxn][maxn];
int main(){
int T=read();
while(T--){
int n=read(),m=read();bool fl=false;
for(int i=1;i<=n;i++)cin>>(ch[i]+1);
LL ans=1;
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
if(ch[i][j]=='0')fl=true;
else (ans*=2LL)%=P;
}
}
if(!fl)ans=(ans-1+P)%P;
printf("%lld\n",(ans+P)%P);
}
return 0;
}
后记
个人感觉本题收获还是比较大的。
- 遇到难想的题抓住题目性质(也许太宽泛了)。
- 抓住部分结构然后合理外推是常见套路,比如:
- 微扰法。
- 各种 \(DP\) 的状态设计和转移分析。
- \(\cdots\)
这些都是需要把握局部的性质然后合理外推。
但是有些题比如组合数学也许就要把握整体了。(下一道)
F. Omkar and Akmar
首先从宏观角度分析:如何才能出现终止状态?
显然是 \(A\) 和 \(B\) 夹着一个空白位置的时候。(局部分析)
所以合理外推,对于终止状态,不难发现:
- 后手一定赢,也就是说 \(A\) 与 \(B\) 一定是成对出现的。
- 不可能出现两个及以上个空位置相连的情况。
有了这两个性质,公式就好写了,可以枚举空位置的个数 \(i\)。
- \(ans=n\sum_{i=n\ mod\ 2}^{\lfloor{n/2}\rfloor}2*C(n-i,i)*(n-i-1)!\)
其中 \(C(n-i,i)\) 为插板法的方案数,\((n-i-1)!\) 是板子的圆排列个数。
每插一次板子等价于填了一个 \(A\) 和一个 \(B\),因为 \(AB\) 和 \(BA\) 是等价的,所以需要乘以 \(2\)。
由于这个形成的圆盘转动后并不是原来的方案了,所以需要乘以 \(n\)。
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
template <typename T>
inline T read(){
T x=0;char ch=getchar();bool fl=false;
while(!isdigit(ch)){
if(ch=='-')fl=true;ch=getchar();
}
while(isdigit(ch)){
x=(x<<3)+(x<<1)+(ch^48);ch=getchar();
}
return fl?-x:x;
}
#define LL long long
const int maxn = 1e6 + 10;
const LL P = 1000000007LL;
LL fac[maxn],invfac[maxn];
int n;
LL power(LL a,LL b){
LL res=1;
while(b){
if(b&1)res=1LL*res*a%P;
a=1LL*a*a%P;
b>>=1;
}
return res;
}
void init(){
fac[0]=1;invfac[0]=1;
for(int i=1;i<=n;i++)fac[i]=1LL*fac[i-1]*i%P;
invfac[n]=power(fac[n],P-2);
for(int i=n;i>1;i--)invfac[i-1]=invfac[i]*i%P;
return ;
}
LL ans=0;
LL C(LL n,LL m){
return fac[n]*invfac[m]%P*invfac[n-m]%P;
}
int main(){
n=read<int>();
init();
for(int i=n%2;i<=(n/2);i+=2){
(ans+=2LL*C(n-i,i)%P*fac[n-i-1]%P)%=P;
}
//n=power(n,P-2);
ans=(1LL*ans*n)%P;
printf("%lld\n",ans);
return 0;
}
后记
对于组合数来说,什么是关键:
- 把握研究对象(比如插板法中的板子)。
- 考虑做到不重不漏(前提是对排列和组合的深刻认识)。
\(P.S.\) 其中 \(E、F\) 题思路均来自@RenaMoe。
但是总是感觉这种思路有点怪异(意义深究的话难以解释),所以有了:
另一种思路
枚举非空格格子的状态。
- \(ans=2\times \sum_{i=2|i}^{n}i!\times (C(n-i,i) + C(n-i-1,i-1))\)
其中 \(i!\) 表示非空格子的全排列(并不等价于圆排列),\(C(n-i,i)\) 表示第一个格子不放空格的情况,\(C(n-i-1,i-1)\) 表示第一个格子放空格的情况。
个人感觉这种思路还是比较清楚的,也就是这个思路巧妙的运用了加法原理使得思路显而易见。
Updated on 2021/10/26
枚举的 \(i\) 并不是空格数,而是 \(A,B\) 的总和数,括号里加起来的方案数是把空格插进去的方案数(第 \(1\) 和第 \(n\) 个位置不能同时放)。
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
template <typename T>
inline T read(){
T x=0;char ch=getchar();bool fl=false;
while(!isdigit(ch)){
if(ch=='-')fl=true;ch=getchar();
}
while(isdigit(ch)){
x=(x<<3)+(x<<1)+(ch^48);ch=getchar();
}
return fl?-x:x;
}
#define int long long
#define LL long long
const int maxn = 1e6 + 10;
const LL P = 1e9+7;
LL fac[maxn],invfac[maxn];
int n;
LL power(LL a,LL b){
LL res=1;
while(b){
if(b&1)res=1LL*res*a%P;
a=1LL*a*a%P;
b>>=1;
}
return res;
}
void init(){
fac[0]=invfac[0]=1;
for(int i=1;i<=n;i++)fac[i]=1LL*fac[i-1]*i%P;
invfac[n]=power(fac[n],P-2);
for(int i=n;i>1;i--)invfac[i-1]=1LL*invfac[i]*i%P;
}
LL ans=0;
LL C(int n,int m){
if(n<m)return 0;
return fac[n]*invfac[m]%P*invfac[n-m]%P;
}
LL f(int n,int m){
return (C(n,m)+C(n-1,m-1))%P;
}
signed main(){
n=read<int>();
init();
for(int i=2;i<=n;i+=2){
//cerr<<ans<<endl;
(ans+=1LL*fac[i]*f(i,n-i)%P)%=P;
}
(ans*=2LL)%=P;
printf("%lld\n",ans);
return 0;
}