A All-Star Game
有 n n n个球员, m m m名粉丝.
我们现在已知一些喜欢关系(粉丝 → \rightarrow →球员).
然后同喜欢一个球员的两个粉丝有相同的喜好.
只有自己喜欢的球员上场自己才去看.
判定至少选多少球员才能让所有粉丝到场.
我们可以考虑可撤销并查集+线段树.
把边的修改范围在线段树上定位,每个节点维护一个
v
e
c
t
o
r
vector
vector,表示正好覆盖这个区间的边.
之所以用到线段树的原因是加入和删除是连续的操作,不会对其他部分造成影响.(回溯得干干净净).
总空间复杂度为
O
(
n
log
n
)
O(n\log n)
O(nlogn),时间复杂度为
O
(
n
log
2
n
)
O(n\log^2 n)
O(nlog2n).
如果考虑用 L C T LCT LCT求的话,可以做到空间 O ( n ) O(n) O(n),时间 O ( n log n ) O(n\log n) O(nlogn).
可撤销并查集:
const int N=5e5+10,M=7e5+10;
int n,m,q,fa[N],sz[N],sum,ans;//sum表示有度的粉丝数量,ans表示粉丝所占的连通块数量.
int get(int x) {return fa[x] == x? x: get(fa[x]);}
vector<int> V[N*2];
map<pii,int> S;//边
struct E{int x,y; } e[M]; int tot,L[M];
struct rec {int x,y,sx,sy,sum,ans;}sta[M]; int top;
//历史修改,可回撤并查集(我们把时间看作一个维度,线段树维护边的出现时间范围).
void pop() {//不可路径压缩,我们按秩合并且暴力回撤
rec t=sta[top--];
fa[t.x]=t.x; fa[t.y]=t.y;
sz[t.x]=t.sx;sz[t.y]=t.sy;
sum=t.sum; ans=t.ans;
}
void merge(int x,int y) { //合并两个点
x=get(x),y=get(y);
sta[++top]=(rec){x,y,sz[x],sz[y],sum,ans};
if(x == y) return ;
if(x <= n && y > n) sum++,ans += (sz[x] == 1);//y>n的话,sz[y]=1.根据下面sz[x]<sz[y] swap得.
else ans -= (sz[x]>1);
if(sz[x]<sz[y]) swap(x,y);
sz[x] += sz[y]; fa[y]=x;
}
void upd(int x,int l,int r,int L,int R,int id) {
if(L<=l&&r<=R) return V[x].pb(id);//放入边.
int mid=(l+r)/2;
if(L<=mid) upd(lc,l,mid,L,R,id);
if(mid< R) upd(rc,mid+1,r,L,R,id);
}
void ask(int x,int l,int r) {
int len=SZ(V[x]);
for(int i=0;i<len;i++) {
int id=V[x][i];
merge(e[id].x,e[id].y);
}
if(l == r) {
if(sum<m) puts("-1");
else pr2(ans);
} else {
int mid=(l+r)/2;
ask(lc,l,mid);
ask(rc,mid+1,r);
}
while(len--) pop();//回撤
}
int main() {
qr(n); qr(m); qr(q);
for(int i=1;i<=n+m;i++) fa[i]=i,sz[i]=1;
for(int x=1,k,y;x<=n;x++) {
qr(k);
while(k--) {
qr(y); y+=n;
S[mk(x,y)]=++tot;
e[tot]=(E){x,y};
L[tot]=1;
}
}
for(int i=1,x,y;i<=q;i++) {
qr(y); qr(x); y += n;
if(!S[mk(x,y)]) {
S[mk(x,y)]=++tot;
e[tot]=(E){x,y};
}
int id=S[mk(x,y)];
if(!L[id]) L[id]=i;
else {
if(i>1) upd(1,1,q,L[id],i-1,id);//[L[id],i)为作用时间.
L[id]=0;
}
}
for(int i=1;i<=tot;i++) if(L[i]) upd(1,1,q,L[i],q,i);
ask(1,1,q); return 0;
}
C Cinema
给定一个 n ∗ m n*m n∗m的矩形.用最少的十字覆盖所有位置,十字中心不能被别的十字覆盖.
状压dp.
定义
0
0
0表示未放置,
1
1
1被十字覆盖但不为中心,
2
2
2为十字中心.
我们可以先搜索出合法的状态,然后搜出合法的转移.
因为询问较多,
m
m
m较小,我们必须要离线处理.
然后处理每个
m
m
m的答案.
记录
f
[
n
]
[
m
]
f[n][m]
f[n][m]为最小放置数量.
p
r
e
[
n
]
[
m
]
pre[n][m]
pre[n][m]为前驱状态.
扫到对应的 n n n时,我们直接把每一行的状态记录下来即可.
(细节有点多,调了很久…)
#include<bits/stdc++.h>
#define fi first
#define se second
#define lc (x<<1)
#define rc (x<<1|1)
#define gc getchar()//(p1==p2&&(p2=(p1=buf)+fread(buf,1,size,stdin),p1==p2)?EOF:*p1++)
#define mk make_pair
#define pii pair<int,int>
#define pll pair<ll,ll>
#define pb push_back
#define IT iterator
#define vi vector<int>
#define TP template<class o>
#define SZ(a) ((int)a.size())
#define all(a) a.begin(),a.end()
using namespace std;
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
const int N=1010,T=6666,size=1<<20,mod=998244353,inf=1e9;
//char buf[size],*p1=buf,*p2=buf;
template<class o> void qr(o &x) {
char c=gc; x=0; int f=1;
while(!isdigit(c)){if(c=='-')f=-1; c=gc;}
while(isdigit(c)) x=x*10+c-'0',c=gc;
x*=f;
}
template<class o> void qw(o x) {
if(x/10) qw(x/10);
putchar(x%10+'0');
}
template<class o> void pr1(o x) {
if(x<0)x=-x,putchar('-');
qw(x); putchar(' ');
}
template<class o> void pr2(o x) {
if(x<0)x=-x,putchar('-');
qw(x); putchar('\n');
}
int n,m,s[T],tot,c[T][3],f[N][T],pre[N][T],p[18];
struct rec {
int s[N],n,m;
} q[N],tmp;
vector<pii> v[17];
vector<int> e[T];//transform
void solve(int x,int y) {
while(x) {
tmp.s[x]=s[y];
y=pre[x--][y];
}
}
int a[18],b[18];
short vis[15010000];
void dfs(int i) {//from 1
if(i>m) {
int x=0;
a[0]=a[m+1]=0;
for(int j=2;j<=m;j++)
if(a[j]==1&&a[j-1]==1) {
if(a[j-2]!=2&&a[j+1]!=2) return ;
}
++tot;
for(int j=0;j<3;j++) c[tot][j]=0;
for(int j=1;j<=m;j++) {
x += a[j]*p[j-1];
if(!vis[x]) vis[x]=-1;
c[tot][a[j]]++;
}
s[tot]=x; vis[x]=tot;
bool flag=1;
for(int j=1;j<=m;j++)
if(a[j]==1) {
if(a[j-1]!=2&&a[j+1]!=2) {flag=0;break;}
}
if(flag)
f[1][tot]=c[tot][2];
else f[1][tot]=inf;
return ;
}
for(int j=0;j<3;j++) {
a[i]=j;
if(j&1) dfs(i+1);
else a[i+1]=1,dfs(i+2);
}
}
int now;
void div(int x) {
for(int i=0;i<m;i++) b[i]=x%3,x /= 3;
}
void calc(int x,int i) {
if(!vis[x]) return;
if(i == m) {
if(vis[x]<0) return;
a[m]=0;
for(int j=0;j<m;j++)
if(a[j]==1&&(!j||a[j-1]!=2)&&a[j+1]!=2&&b[j]!=2) return ;
e[now].pb(vis[x]);
return ;
}
if(!b[i]) a[i]=2,calc(x+2*p[i],i+1);
else if(b[i]==2) a[i]=1,calc(x+p[i],i+1);
else for(int j=0;j<3;j++) a[i]=j,calc(x+p[i]*j,i+1);
}
int main() {
int Q; qr(Q);
for(int i=1,x,y;i<=Q;i++) {
qr(x); qr(y);
v[y].pb(mk(x,i));
}
p[0]=1; for(int i=1;i<=16;i++) p[i]=p[i-1]*3;
// m=15; dfs(1); pr2(tot);
// for(int i=1;i<=tot;i++) div(s[i]),now=i,calc(0,0);
// puts("ok");exit(0);
for(m=1;m<=15;m++) if(SZ(v[m])) {
tot=0; memset(vis,0,sizeof(short)*p[m]); vis[0]=-1;
dfs(1);
for(int i=1;i<=tot;i++) {
now=i;
e[i].clear();
if(s[i]==48)
i++,i--;
div(s[i]),
calc(0,0);
}
int pos=0; tmp.m=m;
sort(all(v[m]));
for(n=1;pos<SZ(v[m]);n++) {
if(v[m][pos].fi == n) {
int mn=inf,p=0;
for(int i=1;i<=tot;i++)
if(!c[i][0] && f[n][i] < mn)
mn=f[n][i], p=i;
solve(n,p); tmp.n=n; tmp.s[0]=mn;
while(pos<SZ(v[m]) && v[m][pos].fi == n)
q[v[m][pos++].se]=tmp;
}
memset(f[n+1]+1,63,sizeof(int)*tot);
for(int i=1;i<=tot;i++) if(f[n][i]<inf)
for(int j:e[i]) {
int t=f[n][i]+c[j][2];
if(f[n+1][j]>t) {
f[n+1][j]=t;
pre[n+1][j]=i;
}
}
}
}
for(int i=1;i<=Q;i++) {
n=q[i].n; m=q[i].m;
printf("Case #%d: %d\n",i,q[i].s[0]);
for(int j=1;j<=n;j++) {
int x=q[i].s[j];
for(int k=0;k<m;k++) {
if(x%3 == 2) putchar('*');
else putchar('.');
// putchar(x%3+'0');
x /= 3;
}
puts("");
}
}
return 0;
}
E Enigmatic Partition
把 n n n划分成 m m m个数 a 1 , a 2 . . . a m a_1,a_2...a_m a1,a2...am.
满足 a i ≤ a i + 1 ≤ a i + 1 , a m = a 1 + 2 a_i\le a_{i+1} \le a_i+1,a_m=a_1+2 ai≤ai+1≤ai+1,am=a1+2.
求划分方案数.
参考博客: https://blog.youkuaiyun.com/qq_45458915/article/details/107777572?utm_medium=distribute.pc_relevant.none-task-blog-baidulandingword-1&spm=1001.2101.3001.4242
神仙差分题.
可以看出我们要用三段数表示
n
n
n.
设三段长度分别为
a
,
b
,
c
a,b,c
a,b,c.第一段的值为
x
x
x,则有:
a
+
b
+
c
=
m
,
x
a
+
(
x
+
1
)
∗
b
+
(
x
+
2
)
∗
c
=
x
m
+
b
+
2
c
=
n
a+b+c=m,xa+(x+1)*b+(x+2)*c=xm+b+2c=n
a+b+c=m,xa+(x+1)∗b+(x+2)∗c=xm+b+2c=n.
我们观察一下,一个较小的情况:(蓝色表示
n
n
n)
我们把 x x x变为 x + 1 x+1 x+1的转移视作高度不变,
把 x + 1 x+1 x+1变为 x + 2 x+2 x+2的转移视作高度+1.
那么从 x , x , x . . . . ( x + 1 ) , ( x + 2 ) ( 111123 ) x,x,x....(x+1),(x+2)(111123) x,x,x....(x+1),(x+2)(111123)出发,每两个可以高度+1.(直到 123333 123333 123333).
于此同时,从 x , ( x + 1 ) , ( x + 2 ) , ( x + 2 ) . . . . ( 122223 ) x,(x+1),(x+2),(x+2)....(122223) x,(x+1),(x+2),(x+2)....(122223)出发,和每加一高度消失一个.(直到 123333 123333 123333后一个位置)
那么,在这个小情况看,就是 13 , 15 13,15 13,15的变化被抵消,也就是可以看作两个都是隔2变化.
此时令 s = a m s=am s=am.
定义差分数组 d d d.
d [ s + 3 ] + + ( 111123 ) , d [ s + m + 1 ] − − ( 222223 ) d[s+3]++(111123),d[s+m+1]--(222223) d[s+3]++(111123),d[s+m+1]−−(222223).
d [ s + m + 2 ] − − ( 222233 ) , d [ s + 2 m − 3 + 3 ] + + ( 333333 ) d[s+m+2]--(222233),d[s+2m-3+3]++(333333) d[s+m+2]−−(222233),d[s+2m−3+3]++(333333).
然后隔项前缀和得到原本的差分值.
得到差分值 后 做二阶前缀和即可.
int T,n;
ll s[N];
int main() {
qr(T); n=1e5;
for(int m=3;m<=n;m++)
for(int a=m;a<=n;a+=m) {
s[a+3]++; s[a+m+1]--;
s[a+m+2]--; s[a+2*m]++;
}
for(int i=2;i<=n;i++) s[i] += s[i-2];
for(int i=1;i<=n;i++) s[i] += s[i-1];
for(int i=1;i<=n;i++) s[i] += s[i-1];
int l,r,num=0;
while(T--) printf("Case #%d: ",++num),qr(l),qr(r),pr2(s[r]-s[l-1]);
return 0;
}
G Game SET
有 n n n个4元组,每个元组有4种可能.(有一种为任选).
如果三个元组的每一位要么全部相同要么全部不同,那么就合法.
尝试找出一个合法的三元组.
对于
∗
*
∗的情况暴力枚举.
然后
n
2
n^2
n2枚举前两个,剩下一个可以用表查出.
int T,n,pos[3][3][3][3];
struct rec {
int a[4];
}e;
vector<rec> a[N],tmp;
string s[4][4]={
{"one","two","three","*"},
{"diamond", "squiggle", "oval","*"},
{"solid", "striped", "open","*"},
{"red", "green", "purple","*"},
};
map<string,int> S[4];
char str[22];
void dfs(rec &a,int i=0) {
if(i==4) return tmp.pb(a);
if(a.a[i]==3) {
for(int j=0;j<3;j++) a.a[i]=j,dfs(a,i+1);
a.a[i]=3;
}
else dfs(a,i+1);
}
void get(char*p) {
char c=gc;
while(!(islower(c)||c=='*') ) c=gc;
while(c!=']') *p++=c,c=gc;
for(int i=0;i<10;i++) *p++=0;
}
int main() {
for(int i=0;i<4;i++)
for(int j=0;j<4;j++)
S[i][s[i][j]]=j;
int num=0;
qr(T); while(T--) {
qr(n); memset(pos,0,sizeof pos);
for(int i=1;i<=n;i++) {
tmp.clear();
for(int j=0;j<4;j++) {
get(str);
e.a[j]=S[j][str];
}
dfs(e); a[i]=tmp;
for(auto j:a[i])
pos[j.a[0]][j.a[1]][j.a[2]][j.a[3]]=i;
}
printf("Case #%d: ",++num);
bool flag=0;
for(int i=1;i<n-1&&!flag;i++)
for(int j=i+1;j<n&&!flag;j++)
for(auto x:a[i]) {
for(auto y:a[j]) {
rec z;
for(int k=0;k<4;k++)
if(x.a[k] == y.a[k]) z.a[k]=x.a[k];
else z.a[k]=3-x.a[k]-y.a[k];
int p=pos[z.a[0]][z.a[1]][z.a[2]][z.a[3]];
if(p>j) {pr1(i); pr1(j); pr2(p); flag=1; break;}
}
if(flag) break;
}
if(!flag )puts("-1");
}
return 0;
}
H Hard String Problem
给定 n n n个串,求 n n n个的无穷循环串的公共子串有多少个.
无穷个则输出-1.
我们先用每个串的最小循环节的最小表示来替换原串.
然后对于两个串的情况,
一个引理是公共子串的长度不会>=3倍长串.
否则,可以推出更小的循环节.
所以我们令每个串都和最短的串求一下公共子串,取交即为答案.
#include<bits/stdc++.h>
#define fi first
#define se second
#define lc (x<<1)
#define rc (x<<1|1)
#define gc getchar()//(p1==p2&&(p2=(p1=buf)+fread(buf,1,size,stdin),p1==p2)?EOF:*p1++)
#define mk make_pair
#define pii pair<int,int>
#define pll pair<ll,ll>
#define pb push_back
#define IT iterator
#define vi vector<int>
#define TP template<class o>
#define SZ(a) ((int)a.size())
#define all(a) a.begin(),a.end()
using namespace std;
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
const int N=3e5+10,size=1<<20,mod=998244353,inf=2e9;
//char buf[size],*p1=buf,*p2=buf;
template<class o> void qr(o &x) {
char c=gc; x=0; int f=1;
while(!isdigit(c)){if(c=='-')f=-1; c=gc;}
while(isdigit(c)) x=x*10+c-'0',c=gc;
x*=f;
}
template<class o> void qw(o x) {
if(x/10) qw(x/10);
putchar(x%10+'0');
}
template<class o> void pr1(o x) {
if(x<0)x=-x,putchar('-');
qw(x); putchar(' ');
}
template<class o> void pr2(o x) {
if(x<0)x=-x,putchar('-');
qw(x); putchar('\n');
}
int n,ans,p[N];
string s[N];
char str[N*2];
int tot=1,last=1,now;
struct node{int v[26],fa,len,vis,t; } tr[N*16];
void add(int c) {
int p=last,x=++tot; last=x; tr[x].len=tr[p].len+1;
for( ;p&&!tr[p].v[c];p=tr[p].fa) tr[p].v[c]=x;
if(!p) tr[x].fa=1;
else {
int q=tr[p].v[c],y;
if(tr[p].len+1 == tr[q].len) tr[x].fa=q;
else {
tr[y=++tot]=tr[q];
tr[y].len=tr[p].len+1;
tr[q].fa=tr[x].fa=y;
for( ;p&&tr[p].v[c] == q;p=tr[p].fa) tr[p].v[c]=y;
}
}
while(x&&tr[x].vis^now) tr[x].vis=now,tr[x].t++,x=tr[x].fa;
}
int getmin(char *s) {//最小循环节
int n=1;
for(int i=2;s[i];i++) {
n++; p[i]=p[i-1];
while(p[i]&&s[p[i]+1]!=s[i]) p[i]=p[p[i]];
p[i] += (s[p[i]+1] == s[i]);
}
int x=n;
while(x) {
x=p[x];
if(n%(n-x)==0) return n-x;
}
return n;
}
string calc(char *s) {//最小表示法
int t=getmin(s);
for(int i=1;i<=t;i++) s[i+t]=s[i];
int i=1,j=2,k;
while(j<=t&&i<=t) {
for(k=0;s[i+k]==s[j+k]&&k<t;k++);
if(k == t) break;
if(s[i+k]<s[j+k]) j += k + 1;
else i += k + 1;
if(i == j) i++;
}
i=min(i,j); s[i+t]=0;
return string(s+i);
}
int LEN;
void debug(int x) {
if(tr[x].t == n) {
str[LEN+1]=0;pr1(x);
puts(str+1);
}
for(int c=0;c<26;c++)
if(tr[x].v[c]) {
str[++LEN]=c+'a';
debug(tr[x].v[c]);
LEN--;
}
}
bool cmp(string &x,string &y) {
return SZ(x)<SZ(y);
}
int main() {
qr(n);
for(int i=1;i<=n;i++) {
scanf("%s",str+1);
s[i]=calc(str);
}
bool flag=1;
for(int i=2;i<=n;i++)
if(s[i]!=s[i-1])
{flag=0; break;}
if(flag) puts("-1"),exit(0);
sort(s+1,s+n+1,cmp);
for(int i=2;i<=n;i++) {
string t=s[i];
s[i] += t+t+t;//公共子串长度<3倍长串,所以要用到4倍长串的长度.
}
string t=s[1];
while(SZ(s[1])<SZ(s[n])) s[1] += t;
for(int i=1;i<=n;i++) {
last=1; now=i;
for(char c:s[i]) add(c-'a');
}
ll ans=0;
// debug(1);
for(int i=2;i<=tot;i++)
if(tr[i].t == n)
ans += tr[i].len-tr[tr[i].fa].len;
pr2(ans); return 0;
}
I Interesting Computer Game
有 n n n个数对 ( a i , b i ) (a_i,b_i) (ai,bi).每次只能选一个数,问最后至多有多少个不同的数.
我们直接给两个数连边,然后答案就是总数字个数-连成树的连通块.
int T,n,fa[N],sz[N],e[N];
map<int,int> s; int tot;
int id(int x) {return !s[x]?s[x]=++tot:s[x];}
int get(int x) {return fa[x]==x?x:fa[x]=get(fa[x]); }
int main() {
qr(T); while(T--) {
qr(n); s.clear(); tot=0;
for(int i=1;i<=n*2;i++) fa[i]=1,sz[i]=1,e[i]=0;
for(int i=1,x,y;i<=n;i++) {
qr(x);qr(y);
x=get(id(x)); y=get(id(y));
if(x==y) e[x]++;
else {
sz[y] += sz[x];
e[y] += e[x] + 1;
fa[x]=y;
}
}
int ans=tot;
pr2(tot);
for(int i=1;i<=tot;i++) if(sz[i]==e[i]+1&&get(i)==i) ans--;
pr2(ans);
}
return 0;
}
K Kabaleo Lite
有 n n n道菜(收益为 a i a_i ai,有 b i b_i bi个).
客人必须有菜,每种菜只能有一个,且菜的种类从1开始连续.
问最大客人数的情况下,最大收益为多少.
不难发现
b
i
b_i
bi受限于
b
i
−
1
.
.
.
b
1
b_{i-1}...b_1
bi−1...b1,先前缀取个
min
\min
min.
然后我们把菜都分给前面的人.
做一次收益的前缀和的最大值即可.
重点在于这题数据卡$long~long
,
只
能
用
,只能用
,只能用__ int128$/高精度卡过.
int T,n,num,a[N],b[N];
ll mx[N];
int main() {
qr(T); b[0]=inf;
while(T--) {
qr(n); __int128 ans=0;
for(int i=1;i<=n;i++) qr(a[i]);
for(int i=1;i<=n;i++) qr(b[i]),b[i]=min(b[i],b[i-1]);
mx[0]=-1e18; ll s=0;
for(int i=1;i<=n;i++) s+=a[i],mx[i]=max(mx[i-1],s);
for(int i=1,pos=n;i<=b[1];i++) {
while(b[pos]<i) pos--;
ans += mx[pos];
}
printf("Case #%d: %d ",++num,b[1]); pr2(ans);
}
return 0;
}