乱七八糟的国庆做题记录
这篇博客只记录杂题/模拟赛补的题/非专项题单里的题
模拟赛T1
赛时糖了,写了个会t的状压还不会处理下界
题面中的限制可以转为:
对于任意合法集合
1.必须包含n的每个质因数的最大次方
2.至少出现一对不同质因数
严肃发现质因子数目比logn还要小的多,可以爆搜
直接算有点难统计,考虑容斥,把所有方案算出来再去掉不合法方案
于是就拥有了一份比std简洁许多的代码,时间复杂度,k为质因数个数
点击查看代码
void dfs(int x,ll s,ll k) { //s为有多少质因子组合成的因数可选,k为容斥系数
if(x>m) {
add(ans,qm(2,s)-1,k);//要把全不选的情况减掉
return ;
} dfs(x+1,s*a[x]%M,k);dfs(x+1,s*(a[x]-1)%M,(M+M-k-k)%M); //所有方案和不合法方案
if(a[x]>2) dfs(x+1,s*(a[x]-2)%M,k); //多减的要补回来
}
void solve() {
freopen("set.in","r",stdin);
freopen("set.out","w",stdout);
cin>>n;
for(ll i=2;i*i<=n;i++) {
if(n%i==0) {
a[++m]=1; //初值设成1方便计算
while(n%i==0) n/=i,a[m]++;
}
} if(n>1) a[++m]=2;
dfs(1,1,1); printf("%lld",ans%M);
}
数据结构糖题
考虑枚举每个点,计算它作为lca时的贡献,自然地,我们需要维护每个点子树里的点到子树跟的路径上有多少不同的颜色,对于单个点的答案,查询其儿子子树中的最大值与次大值再相乘
怎么维护子树中的点到当前点路径上的颜色数呢?我们先考虑链的情况,在从下往上跳的过程中,跳到新点就把下面的点答案全加一,但如果有的点先前往上跳时就遇到了与当前同色的点,那么就加多了,此时我们发现,与当前点颜色相同且距离最近的点下面的点答案都会加多,那么我们需要维护每个点离它最近的同色点,在更新答案时把那个点下面的点答案减一就好了!现在考虑扩展到树,我们发现对于当前点每个儿子的子树内,都要维护最近的同色点,这样我们就成功解决了同色算重的问题,现在我们需要对树上点的答案进行区间加减查询,用线段树就可以维护
核心代码
处理同色点部分
点击查看代码
void dfs(int x) {
dfn[x]=++ct;int idx=c[w[x]];
if(c[w[x]]) e[c[w[x]]].pb(x);
c[w[x]]=x;
for(auto v:a[x]) {
c[w[x]]=x; dfs(v);
} dr[x]=ct; c[w[x]]=idx;
}
维护答案部分
点击查看代码
void dfs1(int x) {
for(auto v:a[x]) dfs1(v);
T.upd(1,dfn[x],dr[x],1,n,1);
for(auto v:e[x]) T.upd(1,dfn[v],dr[v],1,n,-1);
ll m1=1,m2=1;
for(auto v:a[x]) {
int s=T.qry(1,dfn[v],dr[v],1,n);
if(s>m1) m2=max(m2,m1),m1=s;
else if(s>m2) m2=s;
} ans=max(ans,m1*m2);
}
一道巧妙(困难)的图论建模题
解决这道题首先需要想出如何建模,对于每个红点,我们将它的行与列连边,这样染色操作变成删除选定的点的临边(毕竟如果当前点代表的行/列被删掉,那这行/列上的红点也变成白点了,那么我们就无法通过这行/列去操作那些红点),这样建图完美的保留了题目的性质
现在我们已经建好图并转化完操作,考虑怎么操作才能使白点最多
我们略微分析一下,会发现连边后形成了若干个联通块,每个联通块删到最后都必须剩下一个点,这个性质手模一下样例就能发现。
这时我们需要考虑剩下这个点代表的是行还是列,确定联通块个数后,我们可以枚举剩余行的个数,计算白点最大的方案就好了
最后还要输出方案,直接在联通块上遍历dfs树
代码实现
点击查看代码
void dfs(int x) {
vis[x]=1;b[idx].pb(x);
if(x<=r) s1++;
else s2++;
for(auto v:a[x]) if(!vis[v]) dfs(v);
}
void pt(int x,int fa) {
vis[x]=1;
for(auto v:a[x])
if(!vis[v]) pt(v,x);
if(fa) {
if(x>r) printf("Y %d %d\n",fa,x-r);
else printf("X %d %d\n",x,fa-r);
}
}
void solve() {
cin>>r>>c;idx=0;char cc;
for(int i=1;i<=r;i++)
for(int j=1;j<=c;j++) {
cin>>cc;if(cc=='R') Add(i,j+r);
}
for(int i=1;i<=r+c;i++)
if(a[i].size()&&!vis[i]) idx++,dfs(i);
int x=0;
for(int i=1;i<=idx;i++)
if((r-s1+x)*(c-s2+idx-x)>(r-s1+i)*(c-s2+idx-i)) x=i;
printf("%d\n",s1+s2-idx);
for(int i=1;i<=r+c;i++) vis[i]=0;
for(int i=1;i<=x;i++) //保留行
for(auto v:b[i]) if(v<=r) pt(v,0);
for(int i=x+1;i<=idx;i++) //保留列
for(auto v:b[i]) if(v>r) pt(v,0);
}
乍一看似乎不好做,实际上看完数据范围就是大水题
因为可以做,双向搜索,同时从1和n开始搜,表示当前1到点i的距离,n到点j时的距离,形成的回文串串长,对于i和j的儿子们,只要边上的字符相等就可以更新
那么答案怎么统计呢,首先,对于从1和n走到同一个点的状态,肯定可以用来更新答案,然后我们考虑如果回文串长是偶数怎么办,此时只要暴力每个点的相邻点,把加1就好了
点击查看代码
void bfs() {
q.push({1,n});vis[1][n]=1;
while(q.size()) {
int x=q.front().first,y=q.front().second;q.pop();
for(int i=0;i<a[x].size();i++)
for(int j=0;j<a[y].size();j++)
if(b[x][i]==b[y][j]) {
if(vis[a[x][i]][a[y][j]]) continue;
vis[a[x][i]][a[y][j]]=1;
dis[a[x][i]][a[y][j]]=dis[x][y]+2;
q.push({a[x][i],a[y][j]});
}
}
}
void solve() {
cin>>n>>m;int x,y;char c;
for(int i=1;i<=m;i++) cin>>x>>y>>c,Add(x,y,c);
bfs(); int ans=inf;
for(int i=1;i<=n;i++) if(vis[i][i]) ans=min(ans,dis[i][i]);
for(int i=1;i<=n;i++)
for(auto v:a[i]) if(vis[i][v]) ans=min(ans,dis[i][v]+1);
printf("%d",ans==inf?-1:ans);
}
同样都是F,这个题就比上个题难多了,不过计数题自带难度
(刚开始读错题以为同一个对角线也不行)
我们最开始可以列出一个式子:
其中a表示黑棋子占i行k列的方案数,b表示白棋子占j行l列的方案数
这里要注意a和b并非答案,因为我们只是定下了行和列的数量,没用计算选不同行列的方案
接下来考虑怎么算a和b
直接算不太好考虑,我们列一个式子: 其中S代表棋子数
看似很正确,实际上如果有些行和列没有放棋子,这样的方案也被计算进去了,怎么办?我们只需要减去不合法方案数就好了,则有
(应该很好理解吧。。。)
那么这个题就结束了
点击查看代码
void sol(int s,int id) {
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++) {
a[id][i][j]=C[i*j][s];
for(int k=1;k<=i;k++)
for(int l=1;l<=j;l++)
if(k!=i||l!=j) del(a[id][i][j],C[i][k]*C[j][l]%M*a[id][k][l]%M);
}
}
void solve() {
init(); ll ans=0;
cin>>n>>m>>s1>>s2;
sol(s1,0); sol(s2,1);
for(int i=1;i<=n;i++)
for(int j=1;j<=n-i;j++)
for(int k=1;k<=m;k++)
for(int l=1;l<=m-k;l++) add(ans,C[n][i]*C[n-i][j]%M*C[m][k]%M*C[m-k][l]%M
*a[0][i][k]%M*a[1][j][l]%M);
printf("%lld",ans);
}