嘉然!为了你,我要写题解!Codeforces #738(Div.2)
嘉然!为了你,我要写题解!Codeforces Round #738(Div.2)
一个是刚复习完莫比乌斯来写了E题,顺便补了这场的D题(https://codeforces.com/contest/1559/problem/D2),感觉挺妙的,写个题解捏。
因为是赛后补题,有时间看题目背景,于是:
Mocha and Diana are friends in Zhijiang, both of them have a forest with nodes numbered from 1 to n, and they would like to add edges to their forests such that:
我直接补完所有题还写个题解
🤤🤤嘿嘿🤤🤤然然🤤🤤嘿嘿嘿🤤我的然然🤤🤤然然然然🤤🤤🤤我的然然
A. Mocha and Math
https://codeforces.com/contest/1559/problem/A
因为说了“This operation can be performed any number of times.”,那就好办了,只要我们想,我们总有办法让所有数变成\(a_1\&a_2 \&\dots \&a_n\),而AND运算一直做下去肯定不会让答案变大,只有可能变小,所以答案就是把所有\(a_i\)AND起来。
cin>>T;
rep(tc,1,T){
cin>>n;rep(i,1,n)cin>>a[i];
ll ret=a[1];
rep(i,2,n)ret&=a[i];
cout<<ret<<endl;
}
B.Mocha and Red and Blue
https://codeforces.com/contest/1559/problem/B
一个只含BR和?的序列,给?填上B或者R,使得BB或者RR尽可能少。换言之就是要尽可能多的BR,考虑每一段连续的X???X
,其实中间问号怎么填是已经确定下来的:比如如果是B???...X
,就一定是填成BRBR...X
,就算?X
后面变成RR
也没办法,换成B开头也一样会少一个贡献。
所以其实如果从前往后扫一遍就能确定下来所有左边有东西的???
段,然后我再判一下开头就是???X
的情况:
char str[N],ch[]="BR";
vector<pii> seg;
void solve()
{
seg.clear();
for(int i=1;i<=n;i++)if(str[i]=='?')
{
int st=i;
while(i+1<=n&&str[i+1]=='?')i++;
seg.pb(mp(st,i));
}
for(auto itr:seg)
{
if(itr.fi==1)
{
for(int i=itr.se,t=(str[itr.se+1]=='B');i>=1;i--,t^=1)
str[i]=ch[t];
}else
{
for(int i=itr.fi,t=(str[itr.fi-1]=='B');i<=itr.se;i++,t^=1)
str[i]=ch[t];
}
}
}
int main()
{
scanf("%d",&T);
rep(tc,1,T)
{
scanf("%d",&n);
scanf("%s",str+1);
solve();
printf("%s\n",str+1);
}
return 0;
}
C. Mocha and Hiking
https://codeforces.com/contest/1559/problem/C
The city where Mocha lives in is called Zhijiang. There are n+1 villages and 2n−1 directed roads in this city.
🤤这下到枝江了捏。
画一下图,发现如果有01这种结构的出现,那就走\(1\to \dots \to i\to (n+1)\to i+1\to \dots n\)这条路线,否则一定是形如\(11\dots100\dots 00\)的结构,如果有1就从\((n+1)\)出发走一遍,否则全是0就从1开始走,最后走\(n+1\)。综上,符合条件的路径一定存在。
D. Mocha and Diana
https://codeforces.com/contest/1559/problem/D2
大的来了捏
Mocha and Diana are friends in Zhijiang, both of them have a forest with nodes numbered from 1 to n
, and they would like to add edges to their forests such that:
- After adding edges, both of their graphs are still forests.
- They add the same edges. That is, if an edge (u,v)
is added to Mocha's forest, then an edge (u,v)
- is added to Diana's forest, and vice versa.
Mocha and Diana want to know the maximum number of edges they can add, and which edges to add.
D1和D2合起来写了,两张图,保证都是森林,要给\((u,v)\)加边就要同时加边,尽可能多地加一些边使得最后的图还是森林,给出加边的方案。类似的问题似乎之前在组队训练的时候见过一次(不过那题应该比较难的感觉),关键是注意到一些性质:首先说是森林,其实当连通块考虑就行,不用考虑树的具体形态。以及进一步地,连边也可以看成两个连通块连接。
这样这就引出了一个关键的信息:最后连完边一定有一张图只有一个连通块,否则就一定可以继续连边了对吧。进一步因为只有一个连通块,所以连边顺序也无所谓了,于是对于\(n\)比较小的D1题就可以n方地过掉了。
而对于D2,依然是用前面的性质往下想,既然连边顺序无所谓,那一开始就干脆全和1连:记两张图分别为\(G_0,G_1\),\(bl[x]\)为\(x\)所在的连通块,对于\(2,3,\dots,n\)这些点,如果\(bl[i]\)在\(G_0,G_1\)都和\(1\)不连通,那就直接连上\((1,i)\),否则如果\(bl[i]\)在\(G_0\)和1连通,在\(G_1\)和1不连通,以及有一个\(bl[j]\)在\(G_0\)和1不连通,而在\(G_1\)和1连通,那\(bl[i]\)和\(bl[j]\)其实是可以连边的。
于是只要先把第一种情况的连起来,再\(O(n\log n)\)地处理一下剩下的点(因为是连通块,\(bl[i]\)有可能有重复的,去个重捏)
rep(i,2,n)
if(find(0,1)!=find(0,i)&&find(1,1)!=find(1,i))
{
addEdge(0,1,i);addEdge(1,1,i);
ret_edge.pb(mp(1,i));
}
rep(i,2,n)
{
if(find(0,1)!=find(0,i))s1.insert(find(0,i));
if(find(1,1)!=find(1,i))s2.insert(find(1,i));
}
for(auto a=s1.begin(),b=s2.begin();a!=s1.end()&&b!=s2.end();a++,b++)
ret_edge.pb(mp(*a,*b));
cout<<ret_edge.size()<<endl;
for(auto itr:ret_edge)
cout<<itr.fi<<' '<<itr.se<<endl;
E. Mocha and Stars
https://codeforces.com/contest/1559/problem/E
如果没有\(gcd=1\)的约束就是一个比较裸的背包:\(f[i][j]\)表示容量\(j\)的背包装前\(i\)个物品的方案数,\(f[i][j]=\sum_{k=j-r[i]}^{j-l[i]}f[i-1][k]\)这样子,加上前缀和优化就可以\(O(nM)\)地求捏。
加上gcd这个约束之后,很容易让人想到莫比乌斯对吧(でしょう~)
先暴力地把答案写成
\(\begin{aligned}\sum_{a_1=l_1}^{r_1}\dots\sum_{a_n=l_n}^{r_n}[(a_1,\dots,a_n)=1][a_1+\dots+a_n\leq m]\\=\sum_{a_1=l_1}^{r_1}\dots\sum_{a_n}\sum_{d|(a_1,\dots,a_n)}\mu(d)[a_1+\dots+a_n\leq m]\end{aligned}\)
然后就是改写\(a_1,\dots,a_n\),把\(d\)放到前面:
\(\begin{aligned}\sum_{d=1}^m \mu(d) \sum_{a_1=\lceil l_1/d\rceil}^{\lfloor r_1/d \rfloor }\dots \sum_{a_n}[a_1+\dots+a_n\leq \frac{m}{d}]\end{aligned}\)
然后发现后面的问题变成了上面的背包,以及这个背包是\(O(n\frac{m}{d})\)的,调和级数,求和之后就是\(O(nM\log M)\)的。
于是就做完了捏~:
void init()
{
mu[1]=1;
rep(i,2,M-5)
{
if(!not_pri[i])
{
pri_list.pb(i);
mu[i]=-1;
}
for(auto j:pri_list)
{
if(1ll*i*j>M-5)break;
not_pri[i*j]=1;
if(i%j==0){mu[i*j]=0;break;}
mu[i*j]=-mu[i];
}
}
}
ll calc(int d,int mx)
{
rep(i,0,mx)f[0][i]=1;
rep(i,0,mx)s[0][i]=i+1;
rep(i,1,n)
{
int L=(l[i]+d-1)/d,R=r[i]/d;
if(L>R)return 0;
rep(j,0,mx)
{
f[i][j]=((j-L>=0?s[i-1][j-L]:0)-(j-R-1>=0?s[i-1][j-R-1]:0)+MOD)%MOD;
s[i][j]=((j?s[i][j-1]:0)+f[i][j])%MOD;
}
}
return f[n][mx];
}
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
init();
cin>>n>>m;
rep(i,1,n)cin>>l[i]>>r[i];
ll ret=0;
rep(d,1,m)ret=(ret+1ll*mu[d]*calc(d,m/d)%MOD+MOD)%MOD;
cout<<ret;
return 0;
}