[提高组集训2021] Round1
矩阵删除
题目描述
给一个 \(n\times m\)的 \(01\) 矩阵,我们想在每一行删除一个元素,得到一个 \(n\times(m-1)\) 的矩阵。其中删除的元素的位置 \((i,a_i)\),满足 \(|a_i-a_{i+1}|\leq k\)
请问最后能得到多少种本质不同的矩阵,输出答案对 \(1e9+7\) 取模的值。
解法
考虑 \(dp\) 解决这个问题,设 \(f_{i,j}\) 表示第 \(i\) 行删除第 \(j\) 个位置仅考虑前 \(i\) 行得到本质不同的矩阵,转移可以根据题意直接写出,但是很显然本题会算重:
如上图,对前 \(i-1\) 行的某种情况,可能转移到一个连续相同段,我们需要考虑段内的去重,因为删段内得到的 \(i\) 这一行是本质相同的。并且我们不需要考虑不同段内的去重,因为它们得到的 \(i\) 行是一定不同的。
那么段内如何去重呢?考虑一个极长连续相同段 \([l,r]\),设 \(k\in[l,r]\),那么我们只需要考虑 \(k\) 和 \(k+1\) 两者的重复,因为它们得到前 \(i-1\) 行的状态相似性是最高的(类比 \(\tt sa\) 求本质不同子串的去重方式),所以其实把不考虑去重算出来的情况减去所有 \(k\) 和 \(k+1\) 具有的重复就行了。
可以定义辅助数组 \(g_{i,j}\) 表示第 \(i\) 行删除第 \(j-1\) 个位置和删除第 \(j\) 个位置得到矩阵本质相同的方案数,两个状态交替转移即可,\(j-1\) 和 \(j\) 的重复段是 \([j-k,j+k-1]\):
简单前缀和优化即可,时间复杂度 \(O(nm)\)
总结
相似去重法一定要积累下来,找两个最相似的元素去重即可。
#include <cstdio>
#include <iostream>
using namespace std;
const int M = 3005;
const int MOD = 1e9+7;
int read()
{
int x=0,f=1;char c;
while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
int n,m,k,w[M][M],f[M][M],g[M][M];
int main()
{
freopen("matrix.in","r",stdin);
freopen("matrix.out","w",stdout);
n=read();m=read();k=read();
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
scanf("%1d",&w[i][j]);
for(int i=1;i<=m;i++)
{
f[1][i]=1+f[1][i-1];
g[1][i]=(i>1 && w[1][i]==w[1][i-1])+g[1][i-1];
}
for(int i=2;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
int a=min(j+k,m),b=max(j-k-1,0);
int c=min(j+k-1,m),d=max(j-k,0);
f[i][j]=f[i-1][a]-f[i-1][b]
-g[i-1][a]+g[i-1][d];
if(j>1 && w[i][j]==w[i][j-1])
g[i][j]=f[i-1][c]-f[i-1][b]
-g[i-1][c]+g[i-1][d];
}
for(int j=1;j<=m;j++)
{
f[i][j]=(f[i][j]%MOD+MOD)%MOD;
g[i][j]=(g[i][j]%MOD+MOD)%MOD;
f[i][j]=(f[i][j]+f[i][j-1])%MOD;
g[i][j]=(g[i][j]+g[i][j-1])%MOD;
}
}
printf("%d\n",(f[n][m]-g[n][m]+MOD)%MOD);
}
路径查询
题目描述
给你一个 \(n\) 个点 \(m\) 条边的无向图,边有边权,你需要回答 \(q\) 次询问,每次给定两个点 \(u,v\),试求出所有路径中第二大的边权的最小值是多少。
\(1\leq n,q\leq 10^5,1\leq m\leq 2\times 10^5,1\leq w\leq 10^9\)
解法
有一个简单的问题转化,我们从小到大加入边 \(e\),如果此时某个询问 \((u,v)\) 只差一条没有加入的边就能够联通,那么询问 \((u,v)\) 的答案就是 \(e\) 的边权。
自然想到维护每个连通块通过未加入的边能够到达的块外的点集 \(S\),那么询问如何处理呢?一个很神奇的想法是把询问也放在连通块上,维护每个连通块和块外之间的询问集合 \(Q\),关键问题是合并。
要保证复杂度肯定首选启发式合并,这里我们按照 \(S\) 和 \(Q\) 的大小之和来启发式合并,设要把 \(x\) 合并到 \(y\) 上,那么考虑 \(S_x\) 能不能回答 \(Q_y\),\(Q_x\) 能不能被 \(S_y\) 回答,那么我们只需要遍历小的那一边即可。
每个点还是只会被合并 \(\log n\) 次,因为我们还要用 \(\tt set\) 等大常数数据结构,所以时间复杂度 \(O(n\log ^2n)\)
总结
把询问和修改过程一起考虑,那么就能在变化时立即考虑到会被影响的询问。
#include <cstdio>
#include <vector>
#include <iostream>
#include <algorithm>
#include <set>
using namespace std;
const int M = 400005;
#define pii pair<int,int>
#define mp make_pair
int read()
{
int x=0,f=1;char c;
while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
int n,m,k,fa[M],ans[M];set<int> e[M];set<pii> q[M];
struct node
{
int u,v,c;
bool operator < (const node &b) const
{
return c<b.c;
}
}b[M<<1];
int find(int x)
{
if(x!=fa[x]) fa[x]=find(fa[x]);
return fa[x];
}
void merge(int x,int y,int val)
{
if(x==y) return;//no need to merge
fa[x]=y;
//edge of X contribute to the query of Y
for(auto u:e[x]) while(1)//multiple querys
{
set<pii>::iterator it=
q[y].lower_bound(mp(u,0));
if(it==q[y].end() || it->first!=u)
break;//not the query
ans[it->second]=val;
q[y].erase(it);
q[u].erase(mp(y,it->second));
}
//query of X asking the edge of Y
vector<pii> rnm;
for(auto u:q[x]) if(e[y].count(u.first))
{
ans[u.second]=val;
q[u.first].erase(mp(x,u.second));
rnm.push_back(u);
}
for(auto u:rnm) q[x].erase(u);
//add the edge of X to edge of Y
for(auto u:e[x])
{
e[u].erase(x);
if(u!=y)
{
e[u].insert(y);
e[y].insert(u);
}
}
//add the query of X to the query of Y
for(auto u:q[x])
{
q[u.first].erase(mp(x,u.second));
q[u.first].insert(mp(y,u.second));
q[y].insert(u);
}
q[x].clear();e[x].clear();
}
int main()
{
freopen("path.in","r",stdin);
freopen("path.out","w",stdout);
n=read();m=read();k=read();
for(int i=1;i<=n;i++) fa[i]=i;
for(int i=1;i<=m;i++)
{
int u=read(),v=read(),c=read();
e[u].insert(v);
e[v].insert(u);
b[i]=node{u,v,c};
}
for(int i=1;i<=k;i++)
{
int u=read(),v=read();
if(e[u].count(v))
{
ans[i]=-2333;
continue;
}
q[u].insert(mp(v,i));
q[v].insert(mp(u,i));
}
sort(b+1,b+1+m);
for(int i=1;i<=m;i++)
{
int u=find(b[i].u),v=find(b[i].v);
if(e[u].size()+q[u].size()>
e[v].size()+q[v].size()) swap(u,v);
merge(u,v,b[i].c);
}
for(int i=1;i<=k;i++)
{
if(ans[i]==-2333) puts("0");
else if(ans[i]==0) puts("-1");
else printf("%d\n",ans[i]);
}
}