【前言】
本来两周前就差不多写完的,然后去了趟集训就咕掉了。
【题目】
原题地址
给定一个 n × m n\times m n×m的矩阵 a a a,每个位置有一个数字 a i , j a_{i,j} ai,j。现在对第 i i i行和第 j j j列单独考虑,将数字进行正整数离散化,使得行列分别满足相对大小关系。对于每个 ( i , j ) (i,j) (i,j)问使用的数字最大值最小是多少。
n , m ≤ 1000 n,m\leq 1000 n,m≤1000
【解题思路】
题看了半天。
我们可以知道每个数字在每一行每一列的排名,以及每行每列最大排名。这个直接排个序去重就可以了。
然后就是比较一下这行这列这个排名前后部分的大小了。
复杂度 O ( n 2 log n ) O(n^2\log n) O(n2logn)
#include
using namespace std;
const int N=2005;
int n,m,cnt;
int b[N],a[N][N],rk1[N][N],rk2[N][N],mx[N];
int read()
{
int ret=0;char c=getchar();
while(!isdigit(c)) c=getchar();
while(isdigit(c)) ret=ret*10+(c^48),c=getchar();
return ret;
}
void write(int x){if(x>9)write(x/10);putchar(x%10^48);}
void writesp(int x){write(x);putchar(' ');}
int main()
{
#ifdef Durant_Lee
freopen("CF1137A.in","r",stdin);
freopen("CF1137A.out","w",stdout);
#endif
n=read();m=read();
for(int i=1;i<=n;++i) for(int j=1;j<=m;++j) a[i][j]=read();
for(int i=1;i<=n;++i)
{
cnt=0;
for(int j=1;j<=m;++j) b[++cnt]=a[i][j];
sort(b+1,b+cnt+1);mx[i]=cnt=unique(b+1,b+cnt+1)-b-1;
for(int j=1;j<=m;++j) rk1[i][j]=lower_bound(b+1,b+cnt+1,a[i][j])-b;
}
for(int i=1;i<=m;++i)
{
cnt=0;
for(int j=1;j<=n;++j) b[++cnt]=a[j][i];
sort(b+1,b+cnt+1);mx[i+n]=cnt=unique(b+1,b+cnt+1)-b-1;
for(int j=1;j<=n;++j) rk2[j][i]=lower_bound(b+1,b+cnt+1,a[j][i])-b;
}
for(int i=1;i<=n;++i,puts("")) for(int j=1;j<=m;++j)
{
int ans=max(rk1[i][j],rk2[i][j])+max(mx[i]-rk1[i][j],mx[j+n]-rk2[i][j]);
writesp(ans);
}
return 0;
}
给定两个 01 01 01串 S , T S,T S,T,现在可以任意修改 S S S,但要保证其中 01 01 01的个数均不改变,使得 T T T在 S S S中出现的次数尽可能多(可重叠)。
∣ S ∣ , ∣ T ∣ ≤ 5 × 1 0 5 |S|,|T|\leq 5\times 10^5 ∣S∣,∣T∣≤5×105
【解题思路】
KMP \text{KMP} KMP居然有用啊。求出 T T T的 fail \text{fail} fail数组,然后通过这个东西来贪心地匹配一定优秀。
#include
using namespace std;
const int N=5e5+10;
int n,m,f[2],nex[N];
char a[N],b[N];
void output(int x){if(f[x])--f[x],putchar(x^48);else putchar(x^1^48);}
int main()
{
#ifdef Durant_Lee
freopen("CF1137B.in","r",stdin);
freopen("CF1137B.out","w",stdout);
#endif
scanf("%s%s",a+1,b+1);
n=strlen(a+1);m=strlen(b+1);
for(int i=1;i<=n;++i) ++f[a[i]^48];
for(int i=2,j=0;i<=m;++i)
{
for(;j && b[i]!=b[j+1];) j=nex[j];
if(b[i]==b[j+1]) nex[i]=++j;
else nex[i]=0;
}
for(int i=1,j=1;i<=n;++i)
{
output(b[j]^48);++j;
if(j>m) j=nex[m]+1;
}
return 0;
}
一幅 n n n个点 m m m条边的无向图,每个点有一个博物馆。一周有 D D D天,每个博物馆会在 D D D天中固定的一些时间开放。每天游客早上在一个点并参观博物馆(如果开放的话),并在晚上到达下一个城市(如果可以的话)。第一天在 1 1 1号点,随时可以结束行程,最多能访问多少个不同的博物馆。
n , m ≤ 1 0 5 , D ≤ 50 n,m\leq 10^5,D\leq 50 n,m≤105,D≤50
【解题思路】
那么首先我们缩点转 DAG \text{DAG} DAG上进行 DP \text{DP} DP。
不妨先考虑一个强连通分量,那么其访问到每一个点的周期(也就是在 t t t访问到,可以构造在 t + d t+d t+d也能访问到)是里面所有环的 g c d gcd gcd,这个结论之前做题遇到过。
关于求这个周期,在 DFS \text{DFS} DFS树上,非树边连接两个点的深度差 + 1 +1 +1一定形成一个环或者两个环大小的差,全部求一个 g c d gcd gcd即可。
接下来设 f i , j f_{i,j} fi,j表示到第 i i i个 s c c scc scc中在模 D D D意义下时间戳为 j j j的答案。要进行转移,我们首先需要处理出一个 c n t cnt cnt数组,表示在某个时刻 t t t访问到这个点,它能够访问 s c c scc scc上多少个点。还需要处理 s c c scc scc之间的转移边,我们先在每个 s c c scc scc里给里面的节点按照其深度模 d d d编号(任意生成树),记为 v a l val val。若两个 s c c scc scc的周期的 g c d gcd gcd为 G G G,一条边 u , v u,v u,v连接它们,那么这两个 s c c scc scc之间可以连边 ( v a l u − v a l v + 1 ) % G + k ⋅ G (val_u-val_v+1)\% G+k\cdot G (valu−valv+1)%G+k⋅G, k k k是任意整数,当然也是模意义下。
这样总的边数就是 O ( m d ) O(md) O(md),点数是 O ( n ) O(n) O(n),复杂度 O ( m d + n ) O(md+n) O(md+n)。
另一种更简单的做法是,将一个点拆成 d d d个节点,记为 ( i , j ) (i,j) (i,j),点权为 0 0 0或 1 1 1表示这个点 i i i在 j j j天会不会开。对于一条边 ( u , v ) (u,v) (u,v),我们拆成 d d d条边,连边 ( ( u , j ) , ( v , j + 1 ) ) ((u,j),(v,j+1)) ((u,j),(v,j+1)),当然是在模意义下。
这样缩点以后一个 s c c scc scc的权值是里面不同的开启的博物馆个数,直接求最长路即可。
考虑为什么不会算重?若算重,则存在一条路径使得 ( u , j 1 ) (u,j_1) (u,j1)可以到达 ( u , j 2 ) (u,j_2) (u,j2),但不在一个 s c c scc scc中。这条路径一定是一个弱连通环,如果我们将这条路径再走 d − 1 d-1 d−1次一定能再回到 j 1 j_1 j1,因此它们强连通。
复杂度 O ( n d + m ) O(nd+m) O(nd+m)
sol1
#include
#define pb push_back
#define fi first
#define se second
#define mkp make_pair
using namespace std;
typedef pair<int,int> pii;
const int N=2e5+10,M=51;
int D,a[N][M];
int read()
{
int ret=0;char c=getchar();
while(!isdigit(c)) c=getchar();
while(isdigit(c)) ret=ret*10+(c^48),c=getchar();
return ret;
}
namespace Map
{
int tot,ind,scc,nowG;
int head[N],dfn[N],low[N],dep[N],ins[N],tmp[N];
int gd[N],val[N][M],du[N],bl[N],vis[N];
stack<int>stk;
vector<pii>G[N];
struct Tway{int v,nex;}e[N];
void add(int u,int v){e[++tot]=(Tway){v,head[u]};head[u]=tot;}
int gcd(int x,int y){return y?gcd(y,x%y):x;}
void solve(int x,int dp)
{
dep[x]=dp;vis[x]=1;
for(int i=head[x];i;i=e[i].nex)
{
int v=e[i].v;
if(bl[v]^scc) continue;
if(!vis[v]) solve(v,dp+1);
else nowG=gcd(nowG,abs(dep[x]+1-dep[v]));
}
}
void tarjan(int x)
{
dfn[x]=low[x]=++ind;ins[x]=1;stk.push(x);
for(int i=head[x];i;i=e[i].nex)
{
int v=e[i].v;
if(!dfn[v]) tarjan(v),low[x]=min(low[x],low[v]);
else if(ins[v]) low[x]=min(low[x],dfn[v]);
}
if(dfn[x]^low[x]) return;int cnt=0,v=0;
++scc;do{v=stk.top();stk.pop();bl[v]=scc;ins[v]=0;tmp[++cnt]=v;}while(v^x);
nowG=D;solve(x,0);gd[scc]=nowG;
for(int i=0;i<nowG;++i) for(int j=1;j<=cnt;++j) for(int k=i;k<D;k+=nowG)
if(a[tmp[j]][k]){a[tmp[j]][i]=1;break;}
for(int i=0;i<nowG;++i) for(int j=1;j<=cnt;++j)
if(a[tmp[j]][(dep[tmp[j]]+i)%nowG]) ++val[scc][i];
}
}
using namespace Map;
namespace DreamLolita
{
int n,m,ans,f[N][M];
char s[M];
queue<int>q;
void init()
{
n=read();m=read();D=read();
for(int i=1,u,v;i<=m;++i) u=read(),v=read(),add(u,v);
for(int i=1;i<=n;++i)
{
scanf("%s",s);
for(int j=0;j<D;++j) a[i][j]=s[j]=='1';
}
for(int i=1;i<=n;++i) if(!dfn[i]) tarjan(i);
for(int x=1;x<=n;++x) for(int i=head[x];i;i=e[i].nex)
{
int v=e[i].v;
if(bl[x]==bl[v]) continue;
int d=gcd(gd[bl[x]],gd[bl[v]]),ds=(dep[x]-dep[v]+1+D)%D;
for(int j=ds%d;j<D;j+=d) G[bl[x]].pb(mkp(bl[v],j)),du[bl[v]]++;
}
}
void gmax(int &x,int y){x=max(x,y);}
void DP()
{
memset(f,0xc0,sizeof(f));f[bl[1]][0]=val[bl[1]][0];
for(int i=1;i<=scc;++i) if(!du[i]) q.push(i);
while(!q.empty())
{
int x=q.front();q.pop();
for(int t=0;t<D;++t) gmax(ans,f[x][t]);
for(int t=0;t<D;++t) for(auto i:G[x])
{
int v=i.fi,d=i.se;
gmax(f[v][(t+d)%D],f[x][t]+val[v][(t+d)%gd[v]]);
}
for(auto i:G[x]) if(!--du[i.fi]) q.push(i.fi);
}
printf("%d\n",ans);
}
void solution(){init();DP();}
}
int main()
{
#ifdef Durant_Lee
freopen("CF1137C.in","r",stdin);
freopen("CF1137C.out","w",stdout);
#endif
DreamLolita::solution();
return 0;
}
sol2
#include
#define pb push_back
using namespace std;
const int N=1e5+10,M=51,K=N*M+10;
int D;
int read()
{
int ret=0;char c=getchar();
while(!isdigit(c)) c=getchar();
while(isdigit(c)) ret=ret*10+(c^48),c=getchar();
return ret;
}
namespace Map
{
int tot,ind,cnt;
int head[K],low[K],dfn[K],bl[K],a[K],val[K];
bool ins[K];
stack<int>stk;
vector<int>G[K];
set<int>st;
struct Tway{int v,nex;}e[K];
void add(int u,int v){e[++tot]=(Tway){v,head[u]};head[u]=tot;}
void tarjan(int x)
{
dfn[x]=low[x]=++ind;ins[x]=1;stk.push(x);
for(int i=head[x];i;i=e[i].nex)
{
int v=e[i].v;
if(!dfn[v]) tarjan(v),low[x]=min(low[x],low[v]);
else if(ins[v]) low[x]=min(low[x],dfn[v]);
}
if(dfn[x]^low[x]) return;
int v;++cnt;st.clear();
do{v=stk.top();stk.pop();ins[v]=0;bl[v]=cnt;if(a[v])st.insert(v/D);}while(x^v);
val[cnt]=st.size();
}
}
using namespace Map;
namespace DreamLolita
{
int n,m,mx,ans;
int U[N],V[N];
int f[K];
char s[M];
queue<int>q;
int id(int x,int y){return (x-1)*D+y;}
void init()
{
n=read();m=read();D=read();mx=n*D-1;
for(int i=1;i<=m;++i) U[i]=read(),V[i]=read();
for(int i=1;i<=n;++i)
{
scanf("%s",s);
for(int j=0;j<D;++j) a[id(i,j)]=s[j]^48;
}
for(int i=1;i<=m;++i) for(int j=0;j<D;++j) add(id(U[i],j),id(V[i],j+1==D?0:j+1));
for(int i=0;i<=mx;++i) if(!dfn[i]) tarjan(i);
//for(int i=0;i<=mx;++i) printf("(%d,%d) %d\n",i/D+1,i%D,bl[i]);
for(int x=0;x<=mx;++x) for(int i=head[x];i;i=e[i].nex)
{
int v=e[i].v;
if(bl[x]==bl[v]) continue;
G[bl[x]].pb(bl[v]);
}
}
int dfs(int x)
{
if(~f[x]) return f[x];
f[x]=val[x];
for(auto v:G[x]) f[x]=max(f[x],dfs(v)+val[x]);
return f[x];
}
void DP()
{
memset(f,-1,sizeof(f));
int S=bl[id(1,0)];dfs(S);
printf("%d\n",f[S]);
}
void solution(){init();DP();}
}
int main()
{
#ifdef Durant_Lee
freopen("CF1137C.in","r",stdin);
freopen("CF1137C.out","w",stdout);
#endif
DreamLolita::solution();
return 0;
}
交互题
有一张图是由一个长度为 t t t的链和一个大小为 c c c的环中间连上一条边组成的。假如这条边连接的是链的右端点,和环上的 T T T点,令链的左端点是 S S S。
现在在 S S S处有 10 10 10个棋子,编号 0 ∼ 9 0\sim 9 0∼9,每次你可以让任意数量的棋子向出边方向走一步,交互库会返回若干个集合,每一个集合内的棋子都在同一个位置上,并且这个位置上的所有棋子都在这个集合中。
现在你既不知道 t t t也不知道 c c c。你需要使用不超过 3 ( t + c ) 3(t+c) 3(t+c)次操作使得所有棋子都移动到 T T T位置上。
【解题思路】
我们让两个棋子移动,一个每次操作移动一个,另一个每两次操作移动一格,直到两点相遇。
将链和环按顺序编号,假设当 2 2 2移动 t t t格时, 1 1 1在 t + p t+p t+p格,那么 t % c = p t\%c=p t%c=p,此时再走 c − p c-p c−p轮两点会相遇,且位置为 t + c − p t+c-p t+c−p。于是接下来我们将所有点向前移,其他点 t t t轮后到达位置 t t t, 1 , 2 1,2 1,2点 t t t轮后到达 t + c − p + t % c ≡ t t+c-p+t\%c\equiv t t+c−p+t%c≡t
次数就是 3 t + 2 c − 2 p 3t+2c-2p 3t+2c−2p的了。
#include
using namespace std;
char s[20];
int main()
{
for(int x;;)
{
puts("next 0");fflush(stdout);
scanf("%d",&x);for(int i=1;i<=x;++i) scanf("%s",s);
puts("next 0 1");fflush(stdout);
scanf("%d",&x);for(int i=1;i<=x;++i) scanf("%s",s);
if(x==2) break;
}
for(int x;;)
{
puts("next 0 1 2 3 4 5 6 7 8 9");fflush(stdout);
scanf("%d",&x);for(int i=1;i<=x;++i) scanf("%s",s);
if(x==1) break;
}
puts("done");fflush(stdout);
return 0;
}
一列 n n n个车厢的火车,编号 1 ∼ n 1\sim n 1∼n。 Q Q Q次三种操作:
【解题思路】
不难发现对于一次插入,我们只需要维护其左端点编号,其余的一定不会更优。更具体地,如果我们在前端插入,那么后面的所有车厢都没有用了。
如果在后面插入,我们需要维护全局加的一次函数,对于我们之前维护的所有左端点按(车厢编号,权值)可以在二维平面上写成一个个点,只有在左下凸壳上的点才是有用的(即下凸壳去掉斜率为正的部分),维护这个凸壳即可。
复杂度 O ( n ) O(n) O(n),注意这里叉积要用 double \text{double} double或者 int128 \text{int128} int128, long long \text{long long} long long会爆掉。
#include
using namespace std;
typedef long double db;
typedef long long ll;
const int N=3e5+10;
const db eps=1e-10;
int m,top;
ll n,k,b;
int read()
{
int ret=0;char c=getchar();
while(!isdigit(c)) c=getchar();
while(isdigit(c)) ret=ret*10+(c^48),c=getchar();
return ret;
}
struct node
{
ll x,y;
node(ll _x=0,ll _y=0):x(_x),y(_y){}
node operator - (const node&rhs){return node(x-rhs.x,y-rhs.y);}
}q[N];
int O(db x){if(x>eps)return 1;if(x<-eps)return -1;return 0;}
int cross(node a,node b){return O((db)a.x*b.y-(db)a.y*b.x);}
ll calc(node a){return a.x*k+a.y+b;}
int main()
{
#ifdef Durant_Lee
freopen("CF1137E.in","r",stdin);
freopen("CF1137E.out","w",stdout);
#endif
n=read();m=read();q[top=1]=node(0,0);
while(m--)
{
int op=read(),x=read();
if(op==1)
{
q[top=1]=node(0,0);k=0;b=0;n+=x;
}
else if(op==2)
{
node now=node(n,-n*k-b);
while(top>1 && cross(now-q[top-1],q[top]-q[top-1])>=0) --top;
q[++top]=now;n+=x;
}
else b+=x,k+=read();
while(top>1 && calc(q[top])>=calc(q[top-1])) --top;
printf("%lld %lld\n",q[top].x+1,calc(q[top]));
}
return 0;
}
一棵 n n n个点的树,每个点有一个互不相同的权值。定义一次清空为每次删去权值最小的叶子的过程,有 m m m次操作。
【解题思路】
第三个操作可以归到第二个。
首先我们可以 O ( n ) O(n) O(n)知道初始状态下的删除顺序,考虑每次修改对顺序会有什么影响。
假设原最大值为 y y y点,修改的点是 x x x,那么这条链一定是最后被删除的一段,而其他节点的相对删除顺序是不变的。而最后一段的顺序就是从 y y y删到 x x x。
如果我们将修改当作路径染色,那么询问就是比这个节点颜色编号小的节点数加上这个颜色在它之前被染色的数量。
这个染色就很 access \text{access} access了,于是对于前一个部分我们可以用树状数组维护颜色的前缀和,对于后一部分相当于减去同色在它之后染色的数,即这个颜色的 splay \text{splay} splay上它的祖先个数,也即将这个点旋到根后的左子树大小。
#include
using namespace std;
const int N=4e5+10;
namespace IO
{
int read()
{
int ret=0;char c=getchar();
while(!isdigit(c)) c=getchar();
while(isdigit(c)) ret=ret*10+(c^48),c=getchar();
return ret;
}
void write(int x){if(x>9)write(x/10);putchar(x%10^48);}
void writeln(int x){write(x);putchar('\n');}
}
using namespace IO;
namespace Data_Structure
{
struct BIT
{
#define lowbit(x) (x&(-x))
int c[N];
void update(int x,int v){for(;x<N;x+=lowbit(x))c[x]+=v;}
int query(int x){int res=0;for(;x;x-=lowbit(x))res+=c[x];return res;}
#undef lowbit
}bit;
int top,st[N];
struct LCT
{
#define ls ch[x][0]
#define rs ch[x][1]
int rev[N],tar[N],siz[N],fa[N],c[N],ch[N][2];
bool isroot(int x){return ch[fa[x]][0]!=x && ch[fa[x]][1]!=x;}
int get(int x){return ch[fa[x]][1]==x;}
void pushup(int x){siz[x]=siz[ls]+siz[rs]+1;}
void up(int x,int v){c[x]=tar[x]=v;}
void rever(int x){swap(ls,rs);rev[x]^=1;}
void pushdown(int x)
{
if(rev[x]) rever(ls),rever(rs),rev[x]=0;
if(tar[x]) up(ls,tar[x]),up(rs,tar[x]),tar[x]=0;
}
void rotate(int x)
{
int y=fa[x],z=fa[y],k=get(x);
if(!isroot(y)) ch[z][get(y)]=x;
fa[ch[x][!k]]=y;fa[y]=x;fa[x]=z;
ch[y][k]=ch[x][!k];ch[x][!k]=y;
pushup(y);pushup(x);
}
void splay(int x)
{
top=0;st[++top]=x;for(int t=x;!isroot(t);t=fa[t])st[++top]=fa[t];
while(top) pushdown(st[top--]);
while(!isroot(x))
{
int y=fa[x];
if(!isroot(y)) rotate(get(y)==get(x)?y:x);
rotate(x);
}
}
void access(int x,int v)
{
for(int t=0;x;t=x,x=fa[x])
{
splay(x);rs=0;pushup(x);
bit.update(c[x],-siz[x]);up(x,v);bit.update(c[x],siz[x]);
rs=t;pushup(x);
}
}
int query(int x){splay(x);return bit.query(c[x])-siz[ls];}
void update(int x,int v){access(x,v-1);splay(x);rever(x);access(x,v);}
#undef ls
#undef rs
}T;
}
using namespace Data_Structure;
namespace DreamLolita
{
int n,Q,tot;
int head[N];
char op[10];
struct Tway{int v,nex;}e[N<<1];
void add(int u,int v)
{
e[++tot]=(Tway){v,head[u]};head[u]=tot;
e[++tot]=(Tway){u,head[v]};head[v]=tot;
}
void dfs(int x,int f)
{
T.c[x]=x;T.fa[x]=f;T.siz[x]=1;
for(int i=head[x];i;i=e[i].nex)
{
int v=e[i].v;
if(v==f) continue;
dfs(v,x);T.c[x]=max(T.c[x],T.c[v]);
}
for(int i=head[x];i;i=e[i].nex)
{
int v=e[i].v;
if(v==f || T.c[x]^T.c[v]) continue;
T.ch[x][1]=v;
}
T.pushup(x);bit.update(T.c[x],1);
}
void solution()
{
n=read();Q=read();
for(int i=1;i<n;++i) add(read(),read());
dfs(n,0);
while(Q--)
{
scanf("%s",op);int x,y;
if(op[0]=='u') T.update(read(),++n);
else if(op[0]=='w') writeln(T.query(read()));
else x=read(),y=read(),writeln(T.query(x)<T.query(y)?x:y);
}
}
}
int main()
{
#ifdef Durant_Lee
freopen("CF1137F.in","r",stdin);
freopen("CF1137F.out","w",stdout);
#endif
DreamLolita::solution();
return 0;
}