AtCoder Regular Contest

时间:2021-04-24 20:58:35

一句话题解

因为上篇AGC的写的有点长……估计这篇也短不了所以放个一句话题解方便查阅啥的吧QwQ

具体的题意代码题解还是往下翻……

ARC 058

D:简单容斥计数。

E:用二进制表示放的数字,然后状压$DP$。

F:$biset$优化$DP$预处理,乱搞贪心。

ARC 059

D:傻题,存在长的合法子串就一定会存在短的。

E:前缀和优化$DP$。

F:每个长度为$len$的串出现的概率是相同的,求到长度为$len$的方案数然后除$2^{len}$。

ARC 060

D:对$b$分大于根号和小于根号讨论。

E:倍增预处理一个点往右跳$2^i$步后到哪里。

F:$KMP$找循环节。

ARC 061

D:开个$map$直接搞。

E:拆点最短路。

F:组合计数好题。

ARC 062

D:显然交错出是最优的。

F:对点双是否为环讨论一下,组合数+$Polya$计个数。

ARC 063

F:发现答案一定过$y=\frac{h}{2}$或者$x=\frac{w}{2}$,用单调栈和线段树维护一下答案。

ARC 064

F:枚举以$x$的回文串作为原串的循环节,简单容斥一下再分奇偶讨论。

ARC 065

F:设$f[i][j]$表示到了第$i$个位置,可用的$1$还有$j$个。

ARC 066

D:数位$DP$+小$trick$。

E:结论+$DP$。

ARC 068

E:调和级数+反向思维,树状数组维护。

F:性质+$DP$。

ARC 069

F:二分+$2-SAT$+线段树优化建图。

ARC 073

E:分类讨论最大最小值颜色,用$multiset$维护。


ARC 058

D - Iroha and a Grid

题意:

给你一个$H \times\ W$的矩形,左下角有一个$A \times B$的矩形不能走。

只能向右向下走,问从左上角走到右下角的方案数。

题解:

简单容斥,答案为总方案数减去经过左下角矩形的方案数。枚举左下角矩形从哪个位置进入,组合计数一下就好了。

 #include<bits/stdc++.h>
#define N (200009)
#define LL long long
#define MOD (1000000007)
using namespace std; LL inv[N],fac[N],facinv[N];
LL h,w,a,b; void Init()
{
fac[]=facinv[]=inv[]=;
for (int i=; i<=; ++i)
{
if (i!=) inv[i]=(MOD-MOD/i)*inv[MOD%i]%MOD;
fac[i]=fac[i-]*i%MOD; facinv[i]=facinv[i-]*inv[i]%MOD;
}
} LL C(LL n,LL m)
{
if (n<m) return ;
return fac[n]*facinv[m]%MOD*facinv[n-m]%MOD;
} int main()
{
Init();
cin>>h>>w>>a>>b;
LL ans=C(h+w-,h-);
for (int i=; i<=b; ++i)
ans=(ans-C(h-a-+i-,i-)*C(a-+(w-i),a-)%MOD)%MOD;
cout<<(ans+MOD)%MOD;
}

E - Iroha and Haiku

题意:

给你四个值$n,x,y,z$,问你有多少个长度为$n$且只含数字$[1,10]$的序列满足以下条件:

存在连续的三段使得每一段对应和分别为$x,y,z$

题解:

有一个非常奇妙的表示方式,因为发现$x+y+z$并不大,所以可以用$1$表示数字$1$,$10$表示数字$2$,$100$表示数字$3$……比如数字序列$1,2,3$的二进制表示就是$110100$。

设$f[i][S]$表示$DP$到第$i$位,当前不合法状态有多少,且最后$x+y+z$位的状态是什么。显然我们只需要序列二进制表示的最后$x+y+z$位就可以判断当前是否合法了。

答案为$10^n-\sum_{S=0}^{2^{x+y+z}-1} f[n][S]$。

 #include<bits/stdc++.h>
#define LL long long
#define MOD (1000000007)
using namespace std; LL n,x,y,z,ans=,f[][<<]; int main()
{
cin>>n>>x>>y>>z;
for (int i=; i<=n; ++i) ans=ans*%MOD;
f[][]=;
int lim=(<<z-)|(<<y+z-)|(<<x+y+z-);
int state=(<<x+y+z)-;
for (int i=; i<=n; ++i)
for (int S=; S<=state; ++S)
{
if (!f[i][S]) continue;
for (int k=; k<=; ++k)
{
int now=((S<<k)|(<<k-))&state;
if ((now&lim)!=lim) (f[i+][now]+=f[i][S])%=MOD;
}
}
for (int i=; i<=state; ++i)
(ans-=f[n][i])%=MOD;
cout<<(ans+MOD)%MOD<<endl;
}

F -  Iroha Loves Strings

题意:

给你$n$个字符串,选若干个拼成长度为$k$的字符串,问字典序最小的结果。拼接时不能颠倒两个字符串输入的前后关系。

题解:

正解好像非常麻烦……写的是一个暴力版本的做法,但是还是很快的。

首先预处理一个数组$f[i][j]$,表示后$i$个字符串能否拼出长度为$j$的字符串,这个可以用$bitset$加速预处理。

开始前枚举每一个字符串,如果他可以作为答案串的开头,就把他扔到队列里,同时把这个串的$pos$设到开头。

对于$k$位答案一位一位贪心考虑。对当前队列里对所有字符串$pos$位置的字母取$min$输出。

输出完了$check$一下队列里的字符串,如果这个字符串可以被当做答案的当前位,那么这个字符串的$pos$后移一位。否则这个字符串就可以扔掉了。如果一个字符串的$pos$到末尾了,就从他后面的字符串里找出来合法的再加入队列。

 #include<bits/stdc++.h>
#define N (2009)
#define K (10009)
#define S (1000009)
using namespace std; int n,k,cnt,len[N];
char s[N][K];
bitset<K>f[N];
pair<int,int>v[][K]; int main()
{
cin>>n>>k;
for (int i=; i<=n; ++i)
cin>>s[i], len[i]=strlen(s[i]);
f[n+][]=;
for (int i=n; i>=; --i)
f[i]=f[i+]|(f[i+]<<len[i]);
for (int i=; i<=n; ++i)
if (f[i+][k-len[i]])
v[][++cnt]=make_pair(i,);
for (int i=; i<=k; ++i)
{
char Min='z'; int ncnt=;
for (int j=; j<=cnt; ++j)
Min=min(Min,s[v[i-&][j].first][v[i-&][j].second]);
putchar(Min);
int ID=n+;
for (int j=; j<=cnt; ++j)
{
int id=v[i-&][j].first,pos=v[i-&][j].second;
if (s[id][pos]!=Min) continue;
if (pos<len[id]-) v[i&][++ncnt]=make_pair(id,pos+);
else ID=min(ID,id);
}
for (int j=ID+; j<=n; ++j)
if (k-i-len[j]>= && f[j+][k-i-len[j]])
v[i&][++ncnt]=make_pair(j,);
cnt=ncnt;
}
}

ARC 059

D - Unbalanced

题意:

给你一个字符串,找一个子串使得里面存在一个字母出现大于一半次。

题解:

可以发现,如果存在一个长合法子串,那么他里面一定会包含一个短合法子串。证明显然……

 #include<bits/stdc++.h>
#define N (100009)
using namespace std; char s[N]; int main()
{
cin>>s+;
for (int i=,l=strlen(s+); i<=l; ++i)
{
if (i->= && s[i-]==s[i]) {cout<<i-<<' '<<i<<endl; return ;}
if (i->= && s[i-]==s[i]) {cout<<i-<<' '<<i<<endl; return ;}
}
cout<<-<<' '<<-<<endl;
}

E - Children and Candies

题意:

有$n$个人,$c$个糖果,每个人有个值$x_i$,如果第$i$个人分到了$j$个糖果,那么他的高兴度为$x_i^a$。
整体的高兴度为每个人高兴度的乘积。设$f(x_1,x_2,...,x_n)$为所有分配方案高兴度的和。
现在每个人的值是一个范围$[a_i,b_i]$,求

AtCoder Regular Contest

题解:

这个题意简直有毒……

设$f[i][j]$表示前$i$个人分了$j$个苹果的答案,若当前第$i$个人分了$k$个,那么前面的每一种方案都要乘上$\sum_{i=a[i]}^{b[i]} i^m$。这个可以预处理出来,然后$DP$就成了$n^3$的了。

 #include<bits/stdc++.h>
#define N (409)
#define LL long long
#define MOD (1000000007)
using namespace std; LL n,c,a[N],b[N],f[N][N],p[N][N],sum[N][N]; void Init()
{
for (int i=; i<=; ++i)
{
p[i][]=; sum[i][]=;
for (int j=; j<=; ++j) p[i][j]=p[i][j-]*i%MOD;
}
for (int i=; i<=; ++i)
for (int j=; j<=; ++j)
sum[i][j]=(sum[i-][j]+p[i][j])%MOD;
} int main()
{
Init();
cin>>n>>c;
for (int i=; i<=n; ++i) cin>>a[i];
for (int i=; i<=n; ++i) cin>>b[i];
f[][]=;
for (int i=; i<=n; ++i)
for (int j=; j<=c; ++j)
for (int k=; k<=j; ++k)
{
LL t=(sum[b[i]][k]-sum[a[i]-][k]+MOD)%MOD;
(f[i][j]+=f[i-][j-k]*t)%=MOD;
}
cout<<f[n][c]<<endl;
}

F - Unhappy Hacking

题意:

你有$n$次操作,每次要么往后加$0$或$1$,要么删掉末尾字符,如果没字符的时候删除不会发生任何事情。问能打出给定字符串的方法。

题解:

感觉思维有点僵化了啊……记得不久前还和别人提起过类似的来着QAQ

因为长度为$len$的每个串出现的概率是相同的,所以可以直接求到长度为$len$的方案数然后除$2^{len}$。

$f[i][j]$表示进行了$i$次操作,到了$j$位置。转移详见代码。

 #include<bits/stdc++.h>
#define N (5009)
#define MOD (1000000007)
using namespace std; int n,m,f[N][N];
char s[N]; int Qpow(int a,int b)
{
int ans=;
while (b)
{
if (b&) ans=1ll*ans*a%MOD;
a=1ll*a*a%MOD; b>>=;
}
return ans;
} int main()
{
cin>>n>>s;
m=strlen(s);
f[][]=;
for (int i=; i<=n; ++i)
for (int j=; j<=i; ++j)
{
(f[i+][j+]+=f[i][j]*%MOD)%=MOD;
(f[i+][max(j-,)]+=f[i][j])%=MOD;
}
cout<<1ll*f[n][m]*Qpow(Qpow(,m),MOD-)%MOD;;
}

ARC 060

D - Digit Sum

题意:

给你一个$n$和一个$s$,问你$n$在几进制下每一位的十进制和为$s$,有就输出最小的,无解输出$-1$。

题解:

对答案$b$进制分类讨论。

若$b<=\sqrt{n}$,可以直接枚举计算。

若$b>\sqrt{n}$,那么这个数一定是由$a_1 \times b+a_0$构成的。

又因为$s=a_0+a_1$,所以$n-s=(b-1) \times a_1$。

可以枚举$n-s$的因子$a_1$来计算答案。

 #include<bits/stdc++.h>
#define LL long long
using namespace std; LL n,s; LL f(LL b, LL n){return n<b?n:f(b,n/b)+n%b;} int main()
{
cin>>n>>s;
if (s>n) {puts("-1"); return ;}
if (s==n) {cout<<s+<<endl; return ;}
for (int b=; b<=sqrt(n); ++b)
if (f(b,n)==s) {cout<<b<<endl; return ;}
LL ans=1e18;
for (int a1=; a1<=sqrt(n-s); ++a1)
if ((n-s)%a1==)
{
LL b=(n-s)/a1+,a0=s-a1;
if (s-a1>= && a0<b && a1<b && b>=)
ans=min(ans,b);
}
cout<<(ans==1e18?-:ans)<<endl;
}

E - Tak and Hotels

题意:

给你数轴上$n$个点,一次移动距离不能超过$l$,且只能在点上移动。$q$组询问两个点之间的最小移动次数。

题解:

贪心的走的话肯定是一次能走多靠右就多靠右。倍增预处理一个点往右走$2^i$步到哪个点,询问的时候倍增逼近就好了。

 #include<bits/stdc++.h>
#define N (100009)
using namespace std; int n,l,q,a,b,ans,x[N],f[N][]; int main()
{
cin>>n;
for (int i=; i<=n; ++i) cin>>x[i];
cin>>l>>q;
for (int i=; i<=n; ++i)
f[i][]=upper_bound(x+,x+n+,x[i]+l)-x-;
for (int i=; i<=; ++i)
for (int j=; j<=n; ++j)
f[j][i]=f[f[j][i-]][i-];
for (int i=; i<=q; ++i)
{
cin>>a>>b; ans=;
if (a>b) swap(a,b);
for (int i=; i>=; --i)
if (f[a][i]<b) a=f[a][i], ans+=<<i;
ans++;
cout<<ans<<endl;
}
}

F - Best Representation

题意:

当一个字符串没有循环节时,他是合法的。
给出一个字符串$s$,令$F=(f_1,f_2,...,f_m)$,满足$f_i$为$s$的某一部分,$f_1$,$f_2$,...,$f_m$连起来为$s$,并且任意$f_i$为合法。求出$F$表示中最小的$m$.并求出最小$m$的方案数?

题解:

首先需要明确一点,如果一个前缀有循环节,那么$i-next[i]$就是最小的循环节,且不存在更小的。
我们先考虑两种情况:
循环节为$1$。这样怎么划分都不行,所以答案为$len~1$。
字符串$s$不存在循环节。那么答案为$1~1$。
剩下的情况就是字符串$s$包含循环节且循环节长度大于等于$2$,对于这种情况我们可以把前$len-1$分成一段,最后一个单独一段,也就是$m$一定为$2$。
接下来就需要考虑怎么判断字符串划分成两段且都不含循环节的方案数,这个可以用最开始的的那句话,求一下$next$数组就可以$O(n)$扫一遍判断了。

 #include<bits/stdc++.h>
#define N (500009)
using namespace std; int n,ans,f[N],g[N],Next[N];
char s[N]; void Build_Next(char s[],int a[])
{
int k=; memset(Next,,sizeof(Next));
for (int i=; i<=n; ++i)
{
while (k && s[k+]!=s[i]) k=Next[k];
if (s[k+]==s[i]) k++;
Next[i]=k;
}
for (int i=; i<=n; ++i) if (!Next[i] || i%(i-Next[i])) a[i]=;
} int main()
{
cin>>s+; n=strlen(s+);
Build_Next(s,f);
reverse(s+,s+n+);
Build_Next(s,g);
if (f[n]) {puts("1\n1"); return ;}
for (int i=; i<=n; ++i)
if (f[i] && g[n-i]) ans++;
if (!ans) printf("%d\n1\n",n);
else printf("2\n%d\n",ans);
}

ARC 061

D - Snuke's Coloring

题意:

初始有一个$H \times W$的矩阵,进行$n$次操作,第$i$次把$(a_i,b_i)$染黑。最后问对于$0\leq j \leq 9$,包含黑格子个数为$j$的$3 \times 3$的矩形的数量。

题解:

记$Map[i][j]$表示以$(i,j)$为中心的$3 \times 3$的矩形的黑格子数量。每次读入一对就把相邻的八个格子和自己的$Map$更新一下就可以了。

 #include<bits/stdc++.h>
#define LL long long
using namespace std; LL n,h,w,x,y,ans[];
LL dx[]={,,,,,-,-,-,},dy[]={-,,,-,,-,,,};
map<pair<int,int>,int>Map; int main()
{
cin>>h>>w>>n;
ans[]=(h-)*(w-);
for (int i=; i<=n; ++i)
{
cin>>x>>y;
for (int i=; i<; ++i)
{
LL X=x+dx[i],Y=y+dy[i];
if (X< || X>h- || Y< || Y>w-) continue;
LL num=Map[make_pair(X,Y)]++;
ans[num]--; ans[num+]++;
}
}
for (int i=; i<=; ++i) cout<<ans[i]<<endl;
}

E - Snuke's Subway Trip

题意:

给你一张图,每个点有个编号,每次从一条边走到编号不同的边花费为$1$,问从$1$到$n$的最小花费。

题解:

假设我们有一条$u,v,c$的边,连边$u-(u,c)$,$v-(v,c)$,边长为$1$,代表从$u$点/$v$点花费$1$变成了编号为$c$,然后$(u,c)-(v,c)$连边长为$0$的边,代表着两者编号相同不需要花费。建完图跑一遍最短路。因为进入编号$c$和出编号$c$都需要$1$的花费,花费为实际花费的两倍,所以最后答案要除$2$。

 #include<bits/stdc++.h>
#define N (600009)
using namespace std; struct Node
{
int num,dis;
bool operator < (const Node &a) const {return dis>a.dis;}
};
struct Edge{int to,next,len;}edge[N<<];
int n,m,u,v,c,cnt,dis[N],vis[N];
int head[N],num_edge;
priority_queue<Node>q;
map<pair<int,int>,int>Map; void add(int u,int v,int l)
{
edge[++num_edge].to=v;
edge[num_edge].next=head[u];
edge[num_edge].len=l;
head[u]=num_edge;
} void Dijkstra(int s)
{
memset(dis,0x7f,sizeof(dis));
dis[s]=; q.push((Node){s,});
while (!q.empty())
{
int x=q.top().num; q.pop();
if (vis[x]) continue; vis[x]=;
for (int i=head[x]; i; i=edge[i].next)
if (!vis[edge[i].to] && dis[x]+edge[i].len<dis[edge[i].to])
{
dis[edge[i].to]=dis[x]+edge[i].len;
q.push((Node){edge[i].to,dis[edge[i].to]});
}
}
} int getid(int x,int y)
{
if (Map.find(make_pair(x,y))!=Map.end()) return Map[make_pair(x,y)];
return Map[make_pair(x,y)]=++cnt;
} int main()
{
cin>>n>>m; cnt=n;
for (int i=; i<=m; ++i)
{
cin>>u>>v>>c;
int id1=getid(u,c),id2=getid(v,c);
add(id1,id2,); add(id2,id1,);
add(u,id1,); add(id1,u,);
add(v,id2,); add(id2,v,);
}
Dijkstra();
cout<<(dis[n]>2e9?-:dis[n]/)<<endl;
}

F - Card Game for Three

题意:

有三个人$A,B,C$,他们面前分别分别有$n,m,k$张卡片,每张上面写着$a,b,c$三种字母。初始从$A$开始从面前的牌堆里抽卡,抽到哪个字母就哪个人继续从面前抽一张卡,如果有一个人无法操作则这个人获胜。问有多少种可能的初始牌堆能让$A$获胜?

题解:

我们把抽牌的结果搞到一个序列上。例如$A$的初始牌堆为$bcc$,$B$的初始牌堆为$aaa$,$C$的初始牌堆为$bb$,
忽略初始从$A$开始,那么序列就是$b->a->c->b->a->c->b->a$。

有两个比较显然的性质:
1、$a$一旦在序列中出现$n$次,那么$A$立马获胜然后游戏结束,$B$,$C$则需要出现$m+1,k+1$次。因为$A$初始自带一个$a$。
2、抽牌序列最长为$n+m+k$,最短为$n$。

那么我们可以枚举在序列第$i$位游戏结束,然后统计赢家为$A$的合法方案数。
显然第$i$位后面的是啥牌都行反正我们也用不到了,有$3^{n+m+k-i}$的方式。
第$i$位前面,$i-1$个位置需要放$n-1$个$a$,剩下的放$b,c$。
放$a$的方案数是很好求的。
而从$m$个$b$和$k$个$c$选$i-n$个的的方案数设为$f[i-n]$,可以预处理得到
$f[i]=2 \times f[i-1]-C(i-1,m)-C(i-1,k)$。
递推式的含义为:任意一种长度$i-1$的方案,后面加$b$或者加$c$,再减去加上$b$或者加上$c$超限制的方案。
$ans=\sum_{i=n}^{n+m+k} f[i-n] \times C(i-1,n-1) \times 3^{n+m+k-i}$

 #include<bits/stdc++.h>
#define N (1800009)
#define LL long long
#define MOD (1000000007)
using namespace std; LL inv[N],fac[N],facinv[N];
LL n,m,k,ans,f[N]; void Init()
{
fac[]=facinv[]=inv[]=;
for (int i=; i<=; ++i)
{
if (i!=) inv[i]=(MOD-MOD/i)*inv[MOD%i]%MOD;
fac[i]=fac[i-]*i%MOD; facinv[i]=facinv[i-]*inv[i]%MOD;
}
} LL C(LL n,LL m)
{
if (n<m) return ;
return fac[n]*facinv[m]%MOD*facinv[n-m]%MOD;
} LL Qpow(LL a,LL b)
{
LL ans=;
while (b)
{
if (b&) ans=ans*a%MOD;
a=a*a%MOD; b>>=;
}
return ans;
} int main()
{
Init();
cin>>n>>m>>k;
f[]=; f[]=;
for (int i=; i<=m+k; ++i)
f[i]=(f[i-]*-C(i-,m)-C(i-,k))%MOD;
for (int i=n; i<=n+m+k; ++i)
(ans+=f[i-n]*C(i-,n-)%MOD*Qpow(,n+m+k-i)%MOD)%=MOD;
cout<<(ans+MOD)%MOD<<endl;
}

ARC 062

D - AtCoDeer and Rock-Paper

题意:

石头剪刀布,只能出石头和布,知道对面出的规律,且你出的布的总和必须时刻大于石头的总和,最大化赢-输的数量。

题解:

显然石头和布交错着出是最优的。

 #include<bits/stdc++.h>
#define N (100009)
using namespace std; int n,ans;
char s[N]; int main()
{
cin>>s+; n=strlen(s+);
for (int i=; i<=n; ++i)
if (i%== && s[i]=='p') ans--;
else if (i%== && s[i]=='g') ans++;
cout<<ans<<endl;
}

F - Painting Graphs with AtCoDeer

题意:

给你一个无向图和$k$种颜色,你有一个操作是可以把一个简单环上边的颜色沿着环对应旋转,求本质不同的染色方案。

题解:

首先有一个性质,如果一个点双不是一个简单环,那么这个点双可以通过变换形成所有可能的情况。玩过魔方的可能很容易理解。

对每个点双分类讨论:

1、简单环。根据$Polya$定理可以求出方案数,是$\frac{1}{n}\times \sum_{i=1}^{n} 颜色^{置换循环长度}$,在这个题里也就是$\frac{1}{n}\times \sum_{i=1}^{n} k^{gcd(i,n)}$

2、非简单环。现在的本质不同的只和每种颜色的数量有关。插个板可以得到为$C(n+k-1,k-1)$。

3、桥。对答案的贡献为$k$。

 #include<bits/stdc++.h>
#define N (1009)
#define LL long long
#define MOD (1000000007)
using namespace std; struct Edge{int from,to,next;}edge[N<<];
int n,m,k,u,v,s[N];
int Low[N],DFN[N],Stack[N],ID[N],BCC_num,dfs_num,top;
LL ans=,fac[N],facinv[N],inv[N];
int head[N],num_edge;
vector<int>E[N],P[N]; void add(int u,int v)
{
edge[++num_edge].from=u;
edge[num_edge].to=v;
edge[num_edge].next=head[u];
head[u]=num_edge;
} void Init()
{
fac[]=facinv[]=inv[]=;
for (int i=; i<=; ++i)
{
if (i!=) inv[i]=(MOD-MOD/i)*inv[MOD%i]%MOD;
fac[i]=fac[i-]*i%MOD; facinv[i]=facinv[i-]*inv[i]%MOD;
}
} LL gcd(LL a,LL b) {return b==?a:gcd(b,a%b);} LL C(LL n,LL m)
{
if (n<m) return ;
return fac[n]*facinv[m]%MOD*facinv[n-m]%MOD;
} LL Qpow(LL a,LL b)
{
LL ans=;
while (b)
{
if (b&) ans=ans*a%MOD;
a=a*a%MOD; b>>=;
}
return ans;
} LL Polya(int n)
{
LL ans=;
for (int i=; i<=n; ++i) (ans+=Qpow(k,gcd(i,n)))%=MOD;
return ans*inv[n]%MOD;
} void Tarjan(int x,int fa)
{
Low[x]=DFN[x]=++dfs_num;
for (int i=head[x]; i; i=edge[i].next)
if (!DFN[edge[i].to])
{
Stack[++top]=i;
Tarjan(edge[i].to,x);
Low[x]=min(Low[x],Low[edge[i].to]);
if (Low[edge[i].to]>=DFN[x])
{
BCC_num++;
while ()
{
int now=Stack[top--];
E[BCC_num].push_back(now);
if (ID[edge[now].from]!=BCC_num)
{
P[BCC_num].push_back(edge[now].from);
ID[edge[now].from]=BCC_num;
}
if (ID[edge[now].to]!=BCC_num)
{
P[BCC_num].push_back(edge[now].to);
ID[edge[now].to]=BCC_num;
}
if (edge[now].from==x && edge[now].to==edge[i].to) break;
}
int e=,p=;
for (int i=,s=P[BCC_num].size(); i<s; ++i)
{
int x=P[BCC_num][i]; p++;
for (int j=head[x]; j; j=edge[j].next)
if (ID[edge[j].to]==BCC_num) e++;
}
e/=;
if (e==p) (ans*=Polya(e))%=MOD;
else if (p== && e==) (ans*=k)%=MOD;
else (ans*=C(e+k-,k-))%=MOD;
}
}
else if (edge[i].to!=fa)
Low[x]=min(Low[x],DFN[edge[i].to]);
} int main()
{
Init();
cin>>n>>m>>k;
for (int i=; i<=m; ++i)
cin>>u>>v, add(u,v), add(v,u);
for (int i=; i<=n; ++i)
if (!DFN[i]) Tarjan(i,);
cout<<ans<<endl;
}

ARC 063

F - Snuke's Coloring 2

题意:

给你一个$h \times w$的平面和平面内若干点,问平面内不含点的矩形的最大周长。

题解:

可以发现,答案下限为$2 \times max(h,w)+2$,那么比较显然可以得到答案肯定经过$x=\frac{w}{2}$或者$y=\frac{h}{2}$。

下面先就$y=\frac{h}{2}$的情况进行讨论,另一种情况将矩阵旋转一下再做一遍就可以了。

我们维护两个单调栈和一个线段树,一个单调栈维护$y=\frac{h}{2}$上方$y$递增的序列,一个单调栈维护$y=\frac{h}{2}$下方$y$递减的序列,线段树维护当前点$i$到前面某一个点的最大周长的一半(也就是相邻两边之和)。画个图可能更方便理解,更新的时候注意一点细节。

 #include<bits/stdc++.h>
#define N (300009)
using namespace std; struct Node
{
int x,y;
bool operator < (const Node &a) const {return x<a.x;}
}p[N];
struct Sgt{int add,max;}Segt[N<<];
int w,h,n,ans;
int s1[N],s2[N],t1,t2; void Pushdown(int now)
{
Segt[now<<].add+=Segt[now].add;
Segt[now<<|].add+=Segt[now].add;
Segt[now<<].max+=Segt[now].add;
Segt[now<<|].max+=Segt[now].add;
Segt[now].add=;
} void Update(int now,int l,int r,int l1,int r1,int k)
{
if (l>r1 || r<l1) return;
if (l1<=l && r<=r1) {Segt[now].add+=k; Segt[now].max+=k; return;}
int mid=(l+r)>>; Pushdown(now);
Update(now<<,l,mid,l1,r1,k); Update(now<<|,mid+,r,l1,r1,k);
Segt[now].max=max(Segt[now<<].max,Segt[now<<|].max);
} void Solve()
{
memset(Segt,,sizeof(Segt));
sort(p+,p+n+);
s1[t1=]=; s2[t2=]=;
Update(,,n,,n,h);
for (int i=; i<=n; ++i)
{
Update(,,n,,i-,p[i].x-p[i-].x); ans=max(ans,Segt[].max*);
if (p[i].y>=h/)
{
Update(,,n,s1[t1],i-,p[i].y-h);
while (t1> && p[s1[t1]].y>=p[i].y)
Update(,,n,s1[t1-],s1[t1]-,p[i].y-p[s1[t1]].y), t1--;
s1[++t1]=i;
}
else
{
Update(,,n,s2[t2],i-,-p[i].y);
while (t2> && p[s2[t2]].y<=p[i].y)
Update(,,n,s2[t2-],s2[t2]-,p[s2[t2]].y-p[i].y), t2--;
s2[++t2]=i;
}
}
} int main()
{
cin>>w>>h>>n;
for (int i=; i<=n; ++i)
cin>>p[i].x>>p[i].y;
p[++n]=(Node){,}; p[++n]=(Node){w,h};
Solve();
for (int i=; i<=n; ++i) swap(p[i].x,p[i].y);
swap(w,h); Solve();
cout<<ans<<endl;
}

ARC 064

F - Rotated Palindromes

题意:

求长度为$n$且可通过循环位移得到回文串的串的个数。

题解:

首先枚举长度为$x(x|n)$的回文串作为最小循环节。
长度为$x$的循环节有$k^{\left \lceil \frac{x}{2} \right \rceil}$种。但是这个并不是$x$为最小循环节的方案数,而是$x$的约数的方案数的前缀和,可以容斥掉约数的答案。
得到了每个长度$x$作为最小循环节的方案数设为$f[x]$,那么如果$x$是奇数,对答案的贡献为$f[x] \times x$,如果$x$为偶数,对答案的贡献为$f[x] \times \frac{x}{2}$,这个应该还是比较好理解的。

 #include<bits/stdc++.h>
#define MOD (1000000007)
#define LL long long
using namespace std; LL n,k,ans;
vector<LL>v,f; LL Qpow(LL a,LL b)
{
LL ans=;
while (b)
{
if (b&) ans=ans*a%MOD;
a=a*a%MOD; b>>=;
}
return ans;
} int main()
{
cin>>n>>k;
for (int i=; i<=sqrt(n); ++i)
if (n%i==)
{
v.push_back(i);
if (n/i!=i) v.push_back(n/i);
}
sort(v.begin(),v.end());
f.resize(v.size());
for (int i=,sz=v.size(); i<sz; ++i)
{
f[i]=Qpow(k,(v[i]+)/);
for (int j=; j<i; ++j)
if (v[i]%v[j]==) (f[i]-=f[j])%=MOD;
if (v[i]%) (ans+=f[i]*v[i]%MOD)%=MOD;
else (ans+=f[i]*v[i]/%MOD)%=MOD;
}
cout<<(ans+MOD)%MOD<<endl;
}

ARC 065

F - Shuffling

题意:

一个$01$串,$m$次操作,每次将$[l_i,r_i]$随意排列。
保证$l_i$单调不降,问能形成多少不同字符串。

题解:

对于每个位置预处理最右端能到哪个位置,然后设$f[i][j]$表示到了第$i$个位置还有$j$个可用的$1$的方案数。转移看代码吧应该挺好懂的。

 #include<bits/stdc++.h>
#define N (3009)
#define MOD (1000000007)
using namespace std; int n,m,x,y,R[N],sum[N],f[N][N];
char s[N]; int main()
{
cin>>n>>m>>s+; s[n+]='';
for (int i=; i<=n+; ++i)
R[i]=i, sum[i]=sum[i-]+s[i]-'';
for (int i=; i<=m; ++i)
cin>>x>>y, R[x]=max(R[x],y);
for (int i=; i<=n+; ++i)
R[i]=max(R[i],R[i-]);
f[][sum[R[]]]=;
for (int i=; i<=n; ++i)
for (int j=; j<=n; ++j)
{
int k=sum[R[i+]]-sum[R[i]];
if (j) (f[i+][j+k-]+=f[i][j])%=MOD;
if (R[i]-i>=j) (f[i+][j+k]+=f[i][j])%=MOD;
}
cout<<f[n+][]<<endl;
}

ARC 066

D - Xor Sum

题意:

求二元组$(u,v)$的数量,满足$\exists a,b,a~xor~b=u,a+b=v,u\le N,v\le N$。

题解:

首先有一个小$trick$:$a~xor~b=a+b-2(a~and~b)$。

这样的话我们就设$f[(u,v)]$表示两个数的异或和上界为$u$,两个数的和上界为$v$的方案数。

根据那个$trick$我们可以对当前两个的最低位进行讨论:都为$0$,都为$1$,一个为$0$一个为$1$。

感觉还是很巧妙的=v=

 #include<bits/stdc++.h>
#define LL long long
#define MOD (1000000007)
using namespace std; LL n;
map<pair<LL,LL>,int>f; int DFS(LL u,LL v)
{
if (!v) return ;
if (f[make_pair(u,v)]) return f[make_pair(u,v)];
int ans=;
(ans+=DFS(u>>,v>>))%=MOD;
(ans+=DFS((u-)>>,(v-)>>))%=MOD;
if (v>=) (ans+=DFS(u>>,(v-)>>))%=MOD;
return f[make_pair(u,v)]=ans;
} int main()
{
cin>>n;
cout<<DFS(n,n)<<endl;
}

E - Addition and Subtraction Hard

题意:

给你一个形如$a_1±a_2...±a_n$的序列,让你添加括号最大化结果。

题解:

有一个性质,只有减号后面添加括号才有用。

还有一个性质,就是括号不超过两层,因为如果有第三层括号的话可以经过变换把它换成两层。

所以$f[i][0/1/2]$表示到了第$i$个数,结尾有$0/1/2$个右括号的方案数,转移详见代码。

 #include<bits/stdc++.h>
#define N (100009)
#define LL long long
using namespace std; LL n,a[N],f[N][];
char opt[N][]; int main()
{
cin>>n;
for (int i=; i<n; ++i)
cin>>a[i]>>opt[i];
cin>>a[n];
f[][]=a[]; f[][]=f[][]=-1e18;
for (int i=; i<=n; ++i)
if (opt[i-][]=='+')
{
f[i][]=max(f[i-][],f[i-][])+a[i];
f[i][]=f[i-][]-a[i];
f[i][]=f[i-][]+a[i];
}
else
{
f[i][]=-1e18;
f[i][]=max(f[i-][],f[i-][])-a[i];
f[i][]=max(f[i-][],f[i-][])+a[i];
}
cout<<max(f[n][],max(f[n][],f[n][]))<<endl;
}

ARC 068

E - Snuke Line

题意:

有$n$个区间,对于$1≤i≤m$,问有多少个区间至少包含一个$i$的倍数?

题解:

根据调和级数,我们枚举长度从头跳到尾的复杂度是$nlogn$的。

有这么一个性质,假设当前一下跳的长度为$d$,那么长度大于$d$的一定会被经过,这个只需要离线一下就可以随便记录了。长度小于$d$的至多被经过一次,也就是说不会被算重复。

把所有的线段按长度离线下来,从小到大统计就好了。

 #include<bits/stdc++.h>
#define N (300009)
using namespace std; int n,m,l,r,c[N];
vector<int>L[N]; void U(int x,int k) {for (;x<=m; c[x]+=k,x+=(x&-x));}
int Q(int x) {int s=; for (;x; s+=c[x],x-=(x&-x)); return s;} int main()
{
cin>>n>>m;
for (int i=; i<=n; ++i)
cin>>l>>r, L[r-l+].push_back(l);
for (int d=; d<=m; ++d)
{
int ans=n;
for (int i=d; i<=m; i+=d) ans+=Q(i);
for (int i=,sz=L[d].size(); i<sz; ++i)
U(L[d][i],), U(L[d][i]+d,-), --n;
printf("%d\n",ans);
}
}

F - Solitaire

题意:

一个双端队列,把$[1,n]$往里插入,头尾任选。插完后依次取出,每次头尾任选,问第$k$个数为$1$的情况数。

题解:

首先有一个比较显然的事情:插完数后这个双端队列一定是呈$V$字形的,$1$在中间。

当我取到$1$的时候,取出来的数一定是一个或者两个下降序列,这个还是比较容易理解的。

那么就$f[i][j]$表示填完了前$i$个位置,且当前最小的数为$j$的方案数。

如果下一位要填的数比当前小的话,那么$f[i+1][j]$可以从$f[i][j+1...n]$转移,如果比当前大的话,就只能从$f[i][j]$转移。这个可以用一个前缀和优化一下。

 #include<bits/stdc++.h>
#define N (2009)
#define MOD (1000000007)
using namespace std; int n,k,ans,f[N][N],s[N]; int main()
{
cin>>n>>k;
f[][n+]=; s[n+]=;
for (int i=n; i>=; --i) s[i]=s[i+]+f[][i];
for (int i=; i<=k-; ++i)
{
for (int j=; j<=n-i+; ++j) f[i][j]=s[j];
s[n+]=f[i][n+];
for (int j=n; j>=; --j) s[j]=(s[j+]+f[i][j])%MOD;
}
for (int i=; i<=n+; ++i) (ans+=f[k-][i])%=MOD;
for (int i=; i<=n-k-; ++i) (ans*=)%=MOD;
cout<<ans<<endl;
}

ARC 069

F - Flags

题意:

给你$n$对数$(x_i,y_i)$,每对数只能选一个用。最大化数轴上相邻两点间最小距离。

题解:

看到最大化最小值,第一反应就是二分,二分完了变成判断可行性问题,发现可以用$2-SAT$解决。

假设当前二分的值是$lim$,那么对于每个数,选了这个数就不能选和这个数差距小于$lim$的数,这是$2-SAT$经典问题。

但是连的边数是$n^2$的,所以需要优化建图。如果把$2\times n$个数排好序排成一排,可以发现一个数连边的是一个区间,这就可以用线段树优化建图了。让$x$在线段树叶子上的位置存$y$的对应位置,让$y$在线段树叶子上的位置存$x$的对应位置,因为本题$2-SAT$需要连的是另一半。注意不能自己连边到自己的对立节点。

 #include<bits/stdc++.h>
#define N (500009)
using namespace std; struct Node{int id,val;}p[N];
struct Sgt{int ls,rs;}Segt[N<<];
struct Edge{int to,next;}edge[N<<];
int n,x,y,sgt_num,a[N],pos[N],Root;
int DFN[N],Low[N],Vis[N],Color[N];
int Stack[N],top,col_num,dfs_num;
int head[N],num_edge;
bool cmp(Node a,Node b) {return a.val<b.val;} void add(int u,int v)
{
edge[++num_edge].to=v;
edge[num_edge].next=head[u];
head[u]=num_edge;
} int Build(int l,int r)
{
if (l==r) return pos[((p[l].id-)^)+];
int now=++sgt_num;
int mid=(l+r)>>;
Segt[now].ls=Build(l,mid);
Segt[now].rs=Build(mid+,r);
add(now,Segt[now].ls); add(now,Segt[now].rs);
return now;
} void Add_Edge(int now,int l,int r,int l1,int r1,int p)
{
if (l>r1 || r<l1 || l>r) return;
if (l1<=l && r<=r1) {add(p,now); return;}
int mid=(l+r)>>;
Add_Edge(Segt[now].ls,l,mid,l1,r1,p);
Add_Edge(Segt[now].rs,mid+,r,l1,r1,p);
} void Clear()
{
memset(head,,sizeof(head));
memset(DFN,,sizeof(DFN));
memset(Low,,sizeof(Low));
memset(Vis,,sizeof(Vis));
col_num=dfs_num=num_edge=top=;
sgt_num=*n;
} void Tarjan(int x)
{
DFN[x]=Low[x]=++dfs_num;
Vis[x]=; Stack[++top]=x;
for (int i=head[x]; i; i=edge[i].next)
if (!DFN[edge[i].to])
{
Tarjan(edge[i].to);
Low[x]=min(Low[x],Low[edge[i].to]);
}
else if (Vis[edge[i].to])
Low[x]=min(Low[x],DFN[edge[i].to]);
if (DFN[x]==Low[x])
{
Vis[x]=; Color[x]=++col_num;
while (Stack[top]!=x)
{
Color[Stack[top]]=col_num;
Vis[Stack[top--]]=;
}
--top;
}
} bool check(int lim)
{
Clear();
Root=Build(,*n);
for (int i=; i<=*n; ++i)
{
int l=upper_bound(a+,a+*n+,p[i].val-lim)-a;
int r=lower_bound(a+,a+*n+,p[i].val+lim)-a-;
Add_Edge(Root,,*n,max(l,),i-,i);
Add_Edge(Root,,*n,i+,min(r,*n),i);
}
for (int i=; i<=*n; ++i)
if (!DFN[i]) Tarjan(i);
for (int i=; i<=*n; i+=)
if (Color[pos[i]]==Color[pos[i+]]) return false;
return true;
} int main()
{
cin>>n;
for (int i=; i<=n; ++i)
{
cin>>x>>y;
p[i*-]=(Node){i*-,x};
p[i*]=(Node){i*,y};
}
sort(p+,p+*n+,cmp);
for (int i=; i<=*n; ++i)
pos[p[i].id]=i, a[i]=p[i].val;
int l=,r=,ans=-;
while (l<=r)
{
int mid=(l+r)>>;
if (check(mid)) ans=mid, l=mid+;
else r=mid-;
}
cout<<ans<<endl;
}

ARC 073

E - Ball Coloring

题意:

$n$对数,每对数要么被染成红色要么被染成蓝色,最小化两种颜色极差的乘积。

题解:

考虑两种情况:

假设最大值是蓝色,最小值是红色,那么就把一组里面大的扔给蓝色,小的扔给红色,反之亦然。这肯定是最优的。

假设最大值是蓝色,最小值也是蓝色,假设$x_i<y_i$,我们把这些数对按照$x_i$排个序,然后从小往大交换$x_i$和$y_i$并计算答案,可以证明这样是肯定可以取到最优解的。这个可以用$multiset$简单实现。

 #include<bits/stdc++.h>
#define N (200009)
#define LL long long
using namespace std; LL n,ans,x,y;
pair<LL,LL>a[N];
multiset<LL>r,b; int main()
{
cin>>n;
for (int i=; i<=n; ++i)
{
cin>>x>>y;
if (x>y) swap(x,y);
b.insert(x); r.insert(y);
a[i]=make_pair(x,y);
}
sort(a+,a+n+);
ans=(*b.rbegin()-*b.begin())*(*r.rbegin()-*r.begin());
for (int i=; i<=n; ++i)
{
b.erase(b.find(a[i].first)); r.insert(a[i].first);
r.erase(r.find(a[i].second)); b.insert(a[i].second);
ans=min(ans,(*b.rbegin()-*b.begin())*(*r.rbegin()-*r.begin()));
}
cout<<ans<<endl;
}