Day n n n 指第 n n n 个讲课日,模拟赛过屎,不放了。
Day1
单调队列优化 DP
一般形如 d p i = max j ∈ [ l i , r i ] ( f ( j ) ) + g ( j ) dp_i=\max_{j\in[l_i,r_i]}(f(j))+g(j) d p i = max j ∈ [ l i , r i ] ( f ( j )) + g ( j ) 的形式可以进行单调队列优化,其中 f ( i ) f(i) f ( i ) 是一个关于 j j j 的函数,[ l i , r i ] [l_i,r_i] [ l i , r i ] 是滑动窗口。
多重背包优化。O ( n W log m ) O(nW\log m) O ( nW log m ) 的二进制拆分优化就不讲了。
考虑朴素的 O ( n W m ) O(nWm) O ( nWm ) 转移:
d p j = max k = 0 m i ( d p j − k × w i + k × v i ) dp_{j}=\max_{k=0}^{m_i}(dp_{j-k\times w_i}+k\times v_i)
d p j = k = 0 max m i ( d p j − k × w i + k × v i )
尝试改写成适合单调队列优化的形式,按余数分组:
设当前 r = j m o d w i r=j\bmod w_i r = j mod w i ,t = ⌊ j / w i ⌋ t=\lfloor j/w_i\rfloor t = ⌊ j / w i ⌋ ,状态转移方程改写为:
d p j = max k = 0 min ( m i , t ) ( d p r + ( t − k ) ⋅ w i + k ⋅ v i ) dp_j=\max_{k=0}^{\min(m_i,t)}(dp_{r+(t-k)\cdot w_i}+k\cdot v_i)
d p j = k = 0 max m i n ( m i , t ) ( d p r + ( t − k ) ⋅ w i + k ⋅ v i )
令 t ′ = t − k t'=t-k t ′ = t − k ,则 k = t − t ′ k=t-t' k = t − t ′ :
d p j = max ( d p r + t ′ ⋅ w i + ( t − t ′ ) ⋅ v i ) = max ( d p r + t ′ ⋅ w i + t ′ ⋅ v i ) + t ⋅ v i \begin{aligned}
dp_j&=\max(dp_{r+t'\cdot w_i}+(t-t')\cdot v_i)\\
&=\max(dp_{r+t'\cdot w_i}+t'\cdot v_i)+t\cdot v_i
\end{aligned} d p j = max ( d p r + t ′ ⋅ w i + ( t − t ′ ) ⋅ v i ) = max ( d p r + t ′ ⋅ w i + t ′ ⋅ v i ) + t ⋅ v i
t ′ ∈ [ max ( 0 , t − m i ) , t ] t'\in[\max(0,t-m_i),t] t ′ ∈ [ max ( 0 , t − m i ) , t ] ,构成一个滑动窗口。
时间复杂度 O ( n W ) O(nW) O ( nW ) 。
code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 #include <cstdio> #include <cstring> #include <algorithm> using namespace std;template <typename T>inline void read (T &x) { bool f=0 ;x=0 ;char ch=getchar (); while (ch<'0' ||ch>'9' ){ if (ch=='-' ) f=1 ; ch=getchar (); } while (ch>='0' &&ch<='9' ){ x=(x<<1 )+(x<<3 )+(ch^48 ); ch=getchar (); } if (f) x=~x+1 ; } template <typename T,typename ...Args>void read (T &x,Args &...args) {read (x);read (args...);}constexpr int N=4e4 +10 ;struct Queue { int val,t; Queue (){} Queue (int val,int t):val (val),t (t){} }q[N]; int n,W,v,w,m,dp[N],l,r,tag,ans;int main () { read (n,W); memset (dp,0xcf ,sizeof (dp)); dp[0 ]=0 ; for (int i=1 ;i<=n;i++){ read (v,w,m); if (w==0 ){ tag+=v*m; continue ; } for (int d=0 ;d<w;d++){ l=1 ,r=0 ; for (int t=0 ;d+t*w<=W;t++){ int j=t*w+d,val=dp[j]-t*v; while (l<=r&&q[r].val<=val) --r; q[++r]=Queue (val,t); while (l<=r&&q[l].t<t-m) ++l; dp[j]=max (q[l].val+t*v,dp[j]); } } } for (int i=0 ;i<=W;i++) ans=max (ans,dp[i]+tag); printf ("%d\n" ,ans); return 0 ; }
单调队列优化 DP。
容易想到 O ( n m T ) O(nmT) O ( nm T ) 的方程:设 d p t , i , j dp_{t,i,j} d p t , i , j 表示在 ( i , j ) (i,j) ( i , j ) 位置经过 t t t 时间的答案,容易写出转移:
d p t , i , j = max ( d p t − 1 , i , j , d p t − 1 , i ′ , j ′ + 1 ) dp_{t,i,j}=\max(dp_{t-1,i,j},dp_{t-1,i',j'}+1)
d p t , i , j = max ( d p t − 1 , i , j , d p t − 1 , i ′ , j ′ + 1 )
考虑优化成 O ( n m k ) O(nmk) O ( nmk ) ,则我们首先将状态优化为 d p k , i , j dp_{k,i,j} d p k , i , j 表示第 k k k 个区间,在 ( i , j ) (i,j) ( i , j ) 位置的答案。转移变为
d p k , i , j = max ( d p k − 1 , i ′ , j ′ + d i s ) dp_{k,i,j}=\max(dp_{k-1,i',j'}+dis)
d p k , i , j = max ( d p k − 1 , i ′ , j ′ + d i s )
具体地,假设当前在向 i i i 的正方向滑行,我们可以写出:
d p k , i , j = max p o s = i − l e n k i ( d p k − 1 , p o s , j − p o s ) + i dp_{k,i,j}=\max_{pos=i-len_k}^i(dp_{k-1,pos,j}-pos)+i
d p k , i , j = p os = i − l e n k max i ( d p k − 1 , p os , j − p os ) + i
其余三个方向同理,这就可以单调队列优化了。
可以滚动数组去掉 k k k 这一维。
code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 #include <cstdio> #include <cstring> template <typename T>inline void read (T &x) { bool f=0 ;x=0 ;char ch=getchar (); while (ch<'0' ||ch>'9' ){ if (ch=='-' ) f=1 ; ch=getchar (); } while (ch>='0' &&ch<='9' ){ x=(x<<1 )+(x<<3 )+(ch^48 ); ch=getchar (); } if (f) x=~x+1 ; } template <typename T,typename ...Args>void read (T &x,Args &...args) {read (x);read (args...);}template <typename T>inline T Abs (const T &x) {return x<0 ?~x+1 :x;}template <typename T>inline T Max (const T &a,const T &b) {return a<b?b:a;}constexpr int N=210 ,fx[]={0 ,-1 ,1 ,0 ,0 },fy[]={0 ,0 ,0 ,-1 ,1 };struct Queue { int val,x,y; Queue (){} Queue (int val,int x,int y):val (val),x (x),y (y){} }q[N]; inline int dis (const int &ax,const int &ay,const int &bx,const int &by) { return Abs (ax-bx)+Abs (ay-by); } int n,m,sx,sy,k,dp[N][N],op,l,r,ans;char s[N][N];void dfs (int x,int y,int len) { l=1 ,r=0 ; while (x>=1 &&y>=1 &&x<=n&&y<=m){ if (s[x][y]=='x' ){ l=1 ,r=0 ; x+=fx[op],y+=fy[op]; continue ; } while (l<=r&&dp[x][y]>q[r].val+dis (x,y,q[r].x,q[r].y)) --r; q[++r]=Queue (dp[x][y],x,y); while (l<=r&&(Abs (x-q[l].x)>len||Abs (y-q[l].y)>len)) ++l; dp[x][y]=Max (dp[x][y],q[l].val+dis (x,y,q[l].x,q[l].y)); ans=Max (ans,dp[x][y]); x+=fx[op],y+=fy[op]; } } int main () { read (n,m,sx,sy,k); for (int i=1 ;i<=n;i++) for (int j=1 ;j<=m;j++) scanf (" %c" ,&s[i][j]); memset (dp,0xcf ,sizeof (dp)); dp[sx][sy]=0 ; for (int i=1 ,s,t;i<=k;i++){ read (s,t,op); if (op==1 ) for (int j=1 ;j<=m;j++) dfs (n,j,t-s+1 ); else if (op==2 ) for (int j=1 ;j<=m;j++) dfs (1 ,j,t-s+1 ); else if (op==3 ) for (int j=1 ;j<=n;j++) dfs (j,m,t-s+1 ); else for (int j=1 ;j<=n;j++) dfs (j,1 ,t-s+1 ); } printf ("%d\n" ,ans); return 0 ; }
闲话:《海上钢琴师》真的非常好看!
设 d p i , j dp_{i,j} d p i , j 表示 i i i 天结束时手上有 j j j 个股票时的最大收益。转移:
d p i , j = d p i − 1 , j dp_{i,j}=dp_{i-1,j}
d p i , j = d p i − 1 , j
d p i , j = max ( d p i − W − 1 , j − k − k × A P i ) dp_{i,j}=\max(dp_{i-W-1,j-k}-k\times AP_i)
d p i , j = max ( d p i − W − 1 , j − k − k × A P i )
d p i , j = max ( d p i − W − 1 , j + k + k × B P i ) dp_{i,j}=\max(dp_{i-W-1,j+k}+k\times BP_i)
d p i , j = max ( d p i − W − 1 , j + k + k × B P i )
第一个式子没有研究价值,我们看后两个。将式子转化,用 k ′ k' k ′ 分别代替 j − k j-k j − k 和 j + k j+k j + k :
d p i , j = max ( d p i − W − 1 , k ′ + k ′ × A P i ) − j × A P i j − k ′ ≤ A S i dp_{i,j}=\max(dp_{i-W-1,k'}+k'\times AP_i)-j\times AP_i\quad j-k'\le AS_i
d p i , j = max ( d p i − W − 1 , k ′ + k ′ × A P i ) − j × A P i j − k ′ ≤ A S i
d p i , j = max ( d p i − W − 1 , k ′ + k ′ × B P i ) − j × B P i k ′ − j ≤ B S i dp_{i,j}=\max(dp_{i-W-1,k'}+k'\times BP_i)-j\times BP_i\quad k'-j\le BS_i
d p i , j = max ( d p i − W − 1 , k ′ + k ′ × B P i ) − j × B P i k ′ − j ≤ B S i
显然可以单调队列。
code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 #include <cstdio> #include <cstring> #include <algorithm> using namespace std;template <typename T>inline void read (T &x) { bool f=0 ;x=0 ;char ch=getchar (); while (ch<'0' ||ch>'9' ){ if (ch=='-' ) f=1 ; ch=getchar (); } while (ch>='0' &&ch<='9' ){ x=(x<<1 )+(x<<3 )+(ch^48 ); ch=getchar (); } if (f) x=~x+1 ; } template <typename T,typename ...Args>void read (T &x,Args &...args) {read (x);read (args...);}typedef long long ll;constexpr int N=2010 ;int t,maxp,w,l,r,ap[N],dp[N][N],bp[N],as[N],bs[N],ans; struct Queue { int val; ll k; Queue (){} Queue (int val,int k):val (val),k (k){} }q[N]; int main () { read (t,maxp,w); memset (dp,0xcf ,sizeof (dp)); for (int i=1 ;i<=t;i++) read (ap[i],bp[i],as[i],bs[i]); for (int i=1 ;i<=t;i++) for (int j=0 ;j<=as[i];j++) dp[i][j]=-ap[i]*j; for (int i=1 ;i<=t;i++){ for (int j=0 ;j<=maxp;j++) dp[i][j]=max (dp[i][j],dp[i-1 ][j]); l=1 ,r=0 ; int pre=i-w-1 ; if (pre<=0 ) continue ; for (int j=0 ;j<=maxp;j++){ while (l<=r&&q[r].val<=dp[pre][j]+j*ap[i]) --r; q[++r]=Queue (dp[pre][j]+j*ap[i],j); while (l<=r&&j-q[l].k>as[i]) ++l; dp[i][j]=max (dp[i][j],q[l].val-j*ap[i]); } l=1 ,r=0 ; for (int j=maxp;j>=0 ;j--){ while (l<=r&&q[r].val<=dp[pre][j]+j*bp[i]) --r; q[++r]=Queue (dp[pre][j]+j*bp[i],j); while (l<=r&&q[l].k-j>bs[i]) ++l; dp[i][j]=max (dp[i][j],q[l].val-j*bp[i]); } } for (int i=0 ;i<=maxp;i++) ans=max (ans,dp[t][i]); printf ("%d\n" ,ans); return 0 ; }
插一个单调栈典题。
我们考虑枚举行数,对于一行内,维护每一列向上延伸最多的 F
数量(类似直方图)。例如,对于样例
1 2 3 4 5 R F F F F F F F F F F F R R R F F F F F F F F F F F F F F F
假设我们枚举到最后一行,那么应该向上延伸最多的 F
数量应当是:
1 2 3 4 5 6 F F F F F F F F F F F F F F F F F F F F F 2 2 2 5 5 5
此时枚举列,同时对向上延伸最多 F
数量维护单调栈。
时间复杂度 O ( n m ) O(nm) O ( nm ) 。
code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 #include <iostream> using namespace std;constexpr int N=1010 ;struct Stack { int h,len; Stack (){} Stack (int h,int len):h (h),len (len){} }st[N]; int n,m,top,h[N][N],ans;char c;int main () { ios::sync_with_stdio (0 ),cin.tie (0 ),cout.tie (0 ); cin>>n>>m; for (int i=1 ;i<=n;i++) for (int j=1 ;j<=m;j++){ cin>>c; h[i][j]=(c=='F' )?h[i-1 ][j]+1 :0 ; } for (int i=1 ;i<=n;i++){ for (int j=1 ;j<=m;j++){ int temp=0 ; while (top&&st[top].h>h[i][j]){ temp+=st[top].len; ans=max (ans,st[top].h*temp); --top; } st[++top]=Stack (h[i][j],temp+1 ); } int temp=0 ; while (top){ temp+=st[top].len; ans=max (ans,st[top].h*temp); --top; } } cout<<ans*3 <<'\n' ; return 0 ; }
并查集
题意:一个边权为 0 0 0 或 1 1 1 的图,求边权和为 n − k n-k n − k 的生成树。
首先 Kruskal 求最大生成树,求得必须要加入的 0 0 0 边。
再 Kruskal 一遍,先将 0 0 0 边加至 k k k 条,然后一直加 1 1 1 边直到求出生成树。
code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 #include <cstdio> #include <vector> #include <algorithm> using namespace std;template <typename T>inline void read (T &x) { bool f=0 ;x=0 ;char ch=getchar (); while (ch<'0' ||ch>'9' ){ if (ch=='-' ) f=1 ; ch=getchar (); } while (ch>='0' &&ch<='9' ){ x=(x<<1 )+(x<<3 )+(ch^48 ); ch=getchar (); } if (f) x=~x+1 ; } template <typename T,typename ...Args>void read (T &x,Args &...args) {read (x);read (args...);}constexpr int N=2e4 +10 ,M=1e5 +10 ;int n,m,k,fa[N],tot,cnt,ans[N];struct edge {int u,v,w;}e[M];bool l (const edge &x,const edge &y) {return x.w<y.w;}bool g (const edge &x,const edge &y) {return x.w>y.w;}int find (int x) {return fa[x]==x?x:fa[x]=find (fa[x]);}int main () { read (n,m,k); for (int i=1 ;i<=m;i++) read (e[i].u,e[i].v,e[i].w); sort (e+1 ,e+1 +m,g); for (int i=1 ;i<=n;i++) fa[i]=i; for (int i=1 ;i<=m;i++){ int u=find (e[i].u),v=find (e[i].v); if (u==v) continue ; fa[u]=v; ++tot; if (e[i].w==0 ) ++cnt,e[i].w=-1 ; if (tot==n-1 ) break ; } if (cnt>k){ printf ("no solution\n" ); return 0 ; } sort (e+1 ,e+1 +m,l); for (int i=1 ;i<=n;i++) fa[i]=i; tot=0 ; for (int i=1 ;i<=m;i++){ int u=find (e[i].u),v=find (e[i].v); if (u==v) continue ; if (e[i].w==-1 ){ fa[u]=v; e[i].w=0 ; ans[++tot]=i; } else if (tot<k&&e[i].w==0 ){ fa[u]=v; ans[++tot]=i; } else if (tot>=k&&e[i].w==1 ){ fa[u]=v; ans[++tot]=i; } if (tot==n-1 ) break ; } if (tot!=n-1 ){ printf ("no solution\n" ); return 0 ; } for (int i=1 ;i<=tot;i++){ int j=ans[i]; printf ("%d %d %d\n" ,e[j].u,e[j].v,e[j].w); } return 0 ; }
线段树分治+可撤销并查集。
在时间轴上开线段树,每个节点维护 std::vector
表示当前区间需要加的边,使用可撤销并查集在回溯时撤销当前节点添加的边。
code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 #include <cstdio> #include <vector> #include <stack> using namespace std;template <typename T>inline void read (T &x) { bool f=0 ;x=0 ;char ch=getchar (); while (ch<'0' ||ch>'9' ){ if (ch=='-' ) f=1 ; ch=getchar (); } while (ch>='0' &&ch<='9' ){ x=(x<<1 )+(x<<3 )+(ch^48 ); ch=getchar (); } if (f) x=~x+1 ; } template <typename T,typename ...Args>void read (T &x,Args &...args) {read (x);read (args...);}constexpr int N=1e5 +10 ,M=2e5 +10 ;int n,m,fa[N],siz[N],k,last[M];struct Edge { int u,v; Edge (){} Edge (int u,int v):u (u),v (v){} }edge[M]; vector<Edge> tree[N<<2 ]; #define ls (u<<1) #define rs (u<<1|1) void modify (int u,int l,int r,int x,int y,Edge t) { if (x<=l&&y>=r){ tree[u].push_back (t); return ; } int mid=(l+r)>>1 ; if (x<=mid) modify (ls,l,mid,x,y,t); if (y>mid) modify (rs,mid+1 ,r,x,y,t); } int find (int x) {return fa[x]==x?x:find (fa[x]);}stack<Edge> st; inline void merge (int x,int y) { x=find (x),y=find (y); if (x==y) return ; if (siz[x]>siz[y]) swap (x,y); fa[x]=y; siz[y]+=siz[x]; st.push (Edge (x,y)); } void get_ans (int u,int l,int r) { int temp=st.size (); for (Edge x:tree[u]) merge (x.u,x.v); if (l==r){ if (siz[find (1 )]==n) printf ("Connected\n" ); else printf ("Disconnected\n" ); } else { int mid=(l+r)>>1 ; get_ans (ls,l,mid); get_ans (rs,mid+1 ,r); } while ((int )st.size ()>temp){ Edge x=st.top (); int u=x.u,v=x.v; st.pop (); siz[v]-=siz[u]; fa[u]=u; } } int main () { read (n,m); for (int i=1 ;i<=n;i++) fa[i]=i,siz[i]=1 ; for (int i=1 ;i<=m;i++) read (edge[i].u,edge[i].v); read (k); for (int i=1 ,c,x;i<=k;i++){ read (c); while (c--){ read (x); if (last[x]+1 <=i-1 ) modify (1 ,1 ,k,last[x]+1 ,i-1 ,edge[x]); last[x]=i; } } for (int i=1 ;i<=m;i++) modify (1 ,1 ,k,last[i]+1 ,k,edge[i]); get_ans (1 ,1 ,k); return 0 ; }
Day2
Trie
Trie 题怎么能用 Trie 做呢?
由于串长极小,直接字符串哈希,实现时需要注意以下几个点:
使用自然溢出哈希;
记得去重,去重一定不要用 std::set
,会 T 飞。
code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 #include <cstdio> #include <cstring> #include <set> #include <ext/pb_ds/assoc_container.hpp> #include <ext/pb_ds/hash_policy.hpp> using namespace std;using namespace __gnu_pbds;typedef unsigned long long ull;int n,m;ull base[25 ],h[25 ]; gp_hash_table<ull,int > word; char s[25 ];int main () { base[0 ]=1 ; for (int i=1 ;i<=22 ;i++) base[i]=base[i-1 ]*131 ; scanf ("%d%d" ,&n,&m); while (n--){ memset (s,0 ,sizeof (s)); memset (h,0 ,sizeof (h)); scanf ("%s" ,s+1 ); h[0 ]=0 ; int len=strlen (s+1 ); for (int i=1 ;i<=len;i++) h[i]=h[i-1 ]*131 +s[i]; word[h[len]]=m+1 ; } while (m--){ memset (s,0 ,sizeof (s)); memset (h,0 ,sizeof (h)); h[0 ]=0 ; scanf ("%s" ,s+1 ); int len=strlen (s+1 ); for (int i=1 ;i<=len;i++) h[i]=h[i-1 ]*131 +s[i]; if (word.find (h[len])!=word.end ()){ printf ("-1\n" ); continue ; } int res=0 ; for (int i=1 ;i<=len;i++){ ull hash1=h[i-1 ]*base[len-i]+(h[len]-h[i]*base[len-i]); auto it=word.find (hash1); if (it!=word.end ()&&it->second!=m) it->second=m,++res; } for (int i=1 ;i<=len;i++) for (char j='a' ;j<='z' ;j++){ if (j==s[i]) continue ; ull hash1=h[len]-s[i]*base[len-i]+j*base[len-i]; auto it=word.find (hash1); if (it!=word.end ()&&it->second!=m) it->second=m,++res; } for (int i=0 ;i<=len;i++) for (char j='a' ;j<='z' ;j++){ ull hash1=h[i]*base[len-i+1 ]+j*base[len-i]+(h[len]-h[i]*base[len-i]); auto it=word.find (hash1); if (it!=word.end ()&&it->second!=m) it->second=m,++res; } printf ("%d\n" ,res); } return 0 ; }
建 Trie,在 Trie 上查找时对字母连边,拓扑排序判环。
code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 #include <iostream> #include <cstring> #include <queue> using namespace std;constexpr int N=3e5 +10 ;int n,nxt[N][26 ],tot,e[26 ][26 ],indegree[26 ],cnt,ans[N];bool ed[N];string s[N]; inline void insert (string s) { int p=0 ; for (char c:s){ int k=c-'a' ; if (!nxt[p][k]) nxt[p][k]=++tot; p=nxt[p][k]; } ed[p]=1 ; } bool find (string s) { int p=0 ; memset (e,0 ,sizeof (e)); memset (indegree,0 ,sizeof (indegree)); for (char c:s){ if (ed[p]) return 0 ; int k=c-'a' ; for (int i=0 ;i<26 ;i++) if (k!=i&&nxt[p][i]&&!e[k][i]){ e[k][i]=1 ; ++indegree[i]; } p=nxt[p][k]; } queue<int > q; for (int i=0 ;i<26 ;i++) if (!indegree[i]) q.push (i); while (!q.empty ()){ int u=q.front (); q.pop (); for (int v=0 ;v<26 ;v++) if (e[u][v]&&!--indegree[v]) q.push (v); } for (int i=0 ;i<26 ;i++) if (indegree[i]) return 0 ; return 1 ; } int main () { ios::sync_with_stdio (0 ),cin.tie (0 ),cout.tie (0 ); cin>>n; for (int i=1 ;i<=n;i++){ cin>>s[i]; insert (s[i]); } for (int i=1 ;i<=n;i++) if (find (s[i])) ans[++cnt]=i; cout<<cnt<<'\n' ; for (int i=1 ;i<=cnt;i++) cout<<s[ans[i]]<<'\n' ; return 0 ; }
显然先建出 Trie,考虑怎样的遍历顺序更优。
发现这不是老熟人 吗?
code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 #include <iostream> using namespace std;constexpr int N=25010 ;int n,nxt[N*20 ][26 ],tot;bool ed[N*20 ],flag[N*20 ],f;string maxlen,ans; void insert (string s) { int p=0 ; for (char c:s){ int k=c-'a' ; if (!nxt[p][k]) nxt[p][k]=++tot; p=nxt[p][k]; } ed[p]=1 ; } void query (string s) { int p=0 ; for (char c:s){ int k=c-'a' ; p=nxt[p][k]; flag[p]=1 ; } } void dfs (int u) { if (ed[u]) ans.push_back ('P' ); int maxk=-1 ; for (int i=0 ;i<26 ;i++){ int v=nxt[u][i]; if (!v) continue ; if (flag[v]) maxk=i; else { ans.push_back (i+'a' ); dfs (v); } } if (~maxk){ ans.push_back (maxk+'a' ); dfs (nxt[u][maxk]); } if (flag[u]&&!~maxk){ cout<<ans.length ()<<'\n' ; for (char c:ans) cout<<c<<'\n' ; exit (0 ); } ans.push_back ('-' ); } int main () { f=1 ; cin>>n; for (int i=1 ;i<=n;i++){ string s; cin>>s; insert (s); if (s.length ()>maxlen.length ()) maxlen=s; } query (maxlen); dfs (0 ); return 0 ; }
笛卡尔树
考虑 BST 每个节点记录权值 k k k 和插入的时间 t t t ,则 k k k 满足 BST 的性质,t t t 满足小根堆的性质,这样构建的 BST 是一棵 Treap。
题意转化为:重新分配 t t t ,使得生成序列最小。
既然 t t t 维度是小根堆,那么满足父亲小于后代。贪心地想,较小的 t t t 分配顺序为:父亲 > > > 左子树 > > > 右子树,也就是 BST 的前序遍历。
笛卡尔树同样满足 Treap 的性质。它的 k k k 与 t t t 正好与本题的 BST 相反。则我们只需交换 k k k 和 t t t 并 O ( n ) O(n) O ( n ) 构建笛卡尔树即可。
code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 #include <cstdio> template <typename T>inline void read (T &x) { bool f=0 ;x=0 ;char ch=getchar (); while (ch<'0' ||ch>'9' ){ if (ch=='-' ) f=1 ; ch=getchar (); } while (ch>='0' &&ch<='9' ){ x=(x<<1 )+(x<<3 )+(ch^48 ); ch=getchar (); } if (f) x=~x+1 ; } template <typename T,typename ...Args>void read (T &x,Args &...args) {read (x);read (args...);}constexpr int N=1e5 +10 ;int n,a[N],st[N],top,son[N][2 ];bool vis[N];void dfs (int u) { printf ("%d " ,u); if (son[u][0 ]) dfs (son[u][0 ]); if (son[u][1 ]) dfs (son[u][1 ]); } int main () { read (n); for (int i=1 ,x;i<=n;i++) read (x),a[x]=i; for (int i=1 ;i<=n;i++){ int last=0 ; while (top&&a[st[top]]>a[i]) last=st[top--]; if (top) son[st[top]][1 ]=i; son[i][0 ]=last; st[++top]=i; } for (int i=1 ;i<=n;i++) vis[son[i][0 ]]=vis[son[i][1 ]]=1 ; for (int i=1 ;i<=n;i++) if (!vis[i]){ dfs (i); break ; } return 0 ; }
数据结构
可持久化 01 trie 模板题。
操作与主席树类似,对于查询,设我们已经维护了所有的前缀异或和 s u m i = ⊕ j = 1 i a j sum_i=\oplus_{j=1}^ia_j s u m i = ⊕ j = 1 i a j ,则只需最大化 s u m p − 1 ⊕ s u m n ⊕ x sum_{p-1}\oplus sum_n\oplus x s u m p − 1 ⊕ s u m n ⊕ x 。后两项是给定的,则我们只需贪心求出答案即可。
code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 #include <cstdio> #include <algorithm> using std::max;template <typename T>inline void read (T &x) { bool f=0 ;x=0 ;char ch=getchar (); while (ch<'0' ||ch>'9' ){ if (ch=='-' ) f=1 ; ch=getchar (); } while (ch>='0' &&ch<='9' ){ x=(x<<1 )+(x<<3 )+(ch^48 ); ch=getchar (); } if (f) x=~x+1 ; } template <typename T,typename ...Args>void read (T &x,Args &...args) {read (x);read (args...);}constexpr int N=6e5 +10 ;int n,m,nxt[N*25 ][2 ],siz[N*25 ],tot,root[N],sum;inline void insert (int u,int old,int x) { root[u]=++tot; int p=root[u],q=root[old]; siz[p]=siz[q]+1 ; for (int i=24 ;i>=0 ;i--){ int c=(x>>i)&1 ; nxt[p][0 ]=nxt[q][0 ],nxt[p][1 ]=nxt[q][1 ]; nxt[p][c]=++tot; p=nxt[p][c],q=nxt[q][c]; siz[p]=siz[q]+1 ; } } inline int query (int x,int a,int b) { int p=root[a],q=root[b],res=0 ; for (int i=24 ;i>=0 ;i--){ int c=(x>>i)&1 ; if (siz[nxt[q][c^1 ]]>siz[nxt[p][c^1 ]]){ res|=1 <<i; p=nxt[p][c^1 ]; q=nxt[q][c^1 ]; } else { p=nxt[p][c]; q=nxt[q][c]; } } return res; } int main () { read (n,m); insert (0 ,0 ,0 ); for (int i=1 ,a;i<=n;i++){ read (a); insert (i,i-1 ,sum^=a); } char op; int x,l,r; while (m--){ op=getchar (); while (op!='A' &&op!='Q' ) op=getchar (); if (op=='A' ){ read (x); ++n; insert (n,n-1 ,sum^=x); } else { read (l,r,x); int ans; if (l==1 ) ans=max (query (sum^x,0 ,r-1 ),sum^x); else ans=query (sum^x,l-2 ,r-1 ); printf ("%d\n" ,ans); } } return 0 ; }
类似[NOI2010] 超级钢琴 的贪心,由于 a i ⊕ a j = a j ⊕ a i a_i\oplus a_j=a_j\oplus a_i a i ⊕ a j = a j ⊕ a i ,只需使用堆查询最大的 2 k 2k 2 k 个,再令答案除以 2 2 2 。可以用 Trie 查询全局第 k k k 大,方法类似线段树上二分。
code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 #include <cstdio> #include <queue> using namespace std;template <typename T>inline void read (T &x) { bool f=0 ;x=0 ;char ch=getchar (); while (ch<'0' ||ch>'9' ){ if (ch=='-' ) f=1 ; ch=getchar (); } while (ch>='0' &&ch<='9' ){ x=(x<<1 )+(x<<3 )+(ch^48 ); ch=getchar (); } if (f) x=~x+1 ; } template <typename T,typename ...Args>void read (T &x,Args &...args) {read (x);read (args...);}typedef long long ll;constexpr int N=5e5 +10 ;int n,k,nxt[N<<5 ][2 ],siz[N<<5 ],tot=1 ;ll a[N],ans; void insert (ll x) { int p=1 ; ++siz[p]; for (int i=32 ;i>=0 ;i--){ int c=(x>>i)&1 ; if (!nxt[p][c]) nxt[p][c]=++tot; p=nxt[p][c]; ++siz[p]; } } ll query (ll x,int rnk) { int p=1 ; ll res=0 ; for (int i=32 ;i>=0 ;i--){ int c=(x>>i)&1 ; if (siz[nxt[p][c]]>=rnk) p=nxt[p][c]; else { rnk-=siz[nxt[p][c]]; p=nxt[p][c^1 ]; res|=1ll <<i; } } return res; } struct node { int id,rnk; ll val; node (int id,int rnk,ll val):id (id),rnk (rnk),val (val){} bool operator <(const node &x)const {return val<x.val;} }; priority_queue<node> q; int main () { read (n,k);k<<=1 ; insert (0 ); for (int i=1 ;i<=n;i++){ read (a[i]); a[i]^=a[i-1 ]; insert (a[i]); } for (int i=0 ;i<=n;i++) q.push (node (i,n+1 ,query (a[i],n+1 ))); while (k--){ node u=q.top (); q.pop (); ans+=u.val; if (u.rnk) q.push (node (u.id,u.rnk-1 ,query (a[u.id],u.rnk-1 ))); } printf ("%lld\n" ,ans>>1 ); return 0 ; }
Day3
字符串哈希
容易发现,两个回文串 A A A ,B B B 组合成新回文串当且仅当 A B AB A B 等于 B A BA B A 。设 A A A 的哈希值为 a a a ,B B B 的哈希值为 b b b ,有
a × b a s e ∣ B ∣ + b = b × b a s e ∣ A ∣ + a a\times base^{|B|}+b=b\times base^{|A|}+a
a × ba s e ∣ B ∣ + b = b × ba s e ∣ A ∣ + a
简单移项
a b a s e ∣ A ∣ − 1 = b b a s e ∣ B ∣ − 1 \frac{a}{base^{|A|}-1}=\frac{b}{base^{|B|}-1}
ba s e ∣ A ∣ − 1 a = ba s e ∣ B ∣ − 1 b
开哈希桶维护即可。
code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 #include <iostream> #include <ext/pb_ds/assoc_container.hpp> #include <ext/pb_ds/hash_policy.hpp> #include <utility> using namespace std;using namespace __gnu_pbds;typedef long long ll;constexpr int mod1=998244353 ,mod2=1e9 +7 ;int n;ll ans; int qpow (ll a,int b,int p) { a%=p; int res=1 ; while (b){ if (b&1 ) res=res*a%p; a=a*a%p; b>>=1 ; } return res; } struct pair_hash { size_t operator () (const pair<int ,int > &x) const { size_t seed=0 ; seed^=hash<int >{}(x.first)+0x9e3779b9 +(seed<<6 )+(seed>>2 ); seed^=hash<int >{}(x.second)+0x9e3779b9 +(seed<<6 )+(seed>>2 ); return seed; } }; gp_hash_table<pair<int ,int >,int ,pair_hash> cnt; int main () { ios::sync_with_stdio (0 ),cin.tie (0 ),cout.tie (0 ); cin>>n; string s; for (int i=1 ,temp;i<=n;i++){ cin>>temp>>s; ll h1=0 ,h2=0 ,base1=1 ,base2=1 ; for (char c:s){ h1=(h1*131 +c)%mod1; base1=base1*131 %mod1; h2=(h2*131 +c)%mod2; base2=base2*131 %mod2; } ll x=h1*qpow (base1-1 ,mod1-2 ,mod1)%mod1; ll y=h2*qpow (base2-1 ,mod2-2 ,mod2)%mod2; ans+=cnt[make_pair (x,y)]; ++cnt[make_pair (x,y)]; } cout<<ans*2 +n; return 0 ; }
A A A 的一个子串 B B B 能成为 A A A 的一个完整周期,当且仅当其长度为 A A A 长度的因数。直接枚举因数。判断是否是完整周期的方法:设 h l , r h_{l,r} h l , r 表示 [ l , r ] [l,r] [ l , r ] 的哈希值,则当一个长为 l e n len l e n 的子串满足 h l , r − l e n = h l + l e n , r h_{l,r-len}=h_{l+len,r} h l , r − l e n = h l + l e n , r 时,其为一个完整周期。先跑一遍线性筛降低枚举因数的时间复杂度。
code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 #include <cstdio> typedef unsigned long long ull;constexpr int N=5e5 +10 ;int n,q,e[N],p[N],tot;ull h[N],base[N]; char s[N];ull gethash (int l,int r) {return h[r]-h[l-1 ]*base[r-l+1 ];}int main () { scanf ("%d %s %d" ,&n,s+1 ,&q); base[0 ]=1 ; for (int i=1 ;i<=n;i++){ base[i]=base[i-1 ]*131 ; h[i]=h[i-1 ]*131 +s[i]; } for (int i=2 ;i<=n;i++){ if (!e[i]) p[e[i]=++tot]=i; for (int j=1 ;j<=e[i]&&p[j]<=n/i;j++) e[p[j]*i]=j; } int a,b,len,ans; while (q--){ scanf ("%d %d" ,&a,&b); len=ans=b-a+1 ; while (len>1 ){ int prime=p[e[len]]; if (gethash (a+ans/prime,b)==gethash (a,b-ans/prime)) ans/=prime; len/=prime; } printf ("%d\n" ,ans); } return 0 ; }
双指针+二分+哈希。
code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 #include <cstdio> #include <algorithm> using namespace std;template <typename T>inline void read (T &x) { bool f=0 ;x=0 ;char ch=getchar (); while (ch<'0' ||ch>'9' ){ if (ch=='-' ) f=1 ; ch=getchar (); } while (ch>='0' &&ch<='9' ){ x=(x<<1 )+(x<<3 )+(ch^48 ); ch=getchar (); } if (f) x=~x+1 ; } template <typename T,typename ...Args>void read (T &x,Args &...args) {read (x);read (args...);}typedef unsigned long long ull;constexpr int N=2e5 +10 ;int n,m,a[N],b[N],len,p1,p2;ull h1[N],h2[N],base[N]; inline ull gethash1 (int l,int r) {return h1[r]-h1[l-1 ]*base[r-l+1 ];}inline ull gethash2 (int l,int r) {return h2[r]-h2[l-1 ]*base[r-l+1 ];}inline bool check (int x) {return gethash1 (p1,p1+x-1 )==gethash2 (p2,p2+x-1 );}int main () { read (n); for (int i=1 ;i<=n;i++) read (a[i]); read (m); for (int i=1 ;i<=m;i++) read (b[i]); if (n<m) for (int i=n+1 ;i<=m;i++) a[i]=1e3 +1 ; if (n>m) for (int i=m+1 ;i<=n;i++) b[i]=1e3 +1 ; base[0 ]=1 ; len=max (n,m); a[len+1 ]=b[len+1 ]=1e3 +1 ; for (int i=1 ;i<=len;i++) base[i]=base[i-1 ]*13331 ; for (int i=1 ;i<=len;i++) h1[i]=h1[i-1 ]*13331 +a[i]; for (int i=1 ;i<=len;i++) h2[i]=h2[i-1 ]*13331 +b[i]; p1=1 ,p2=1 ; for (int i=1 ;i<=n+m;i++){ if (a[p1]<b[p2]) printf ("%d " ,a[p1++]); else if (a[p1]>b[p2]) printf ("%d " ,b[p2++]); else { int l=1 ,r=min (len-p1+1 ,len-p2+1 ); while (l<=r){ int mid=(l+r)>>1 ; if (check (mid)) l=mid+1 ; else r=mid-1 ; } if (a[p1+r]<b[p2+r]) printf ("%d " ,a[p1++]); else printf ("%d " ,b[p2++]); } } return 0 ; }
KMP
设 f i , j f_{i,j} f i , j 表示长串匹配前 i i i 个字符,短串匹配前 j j j 个字符的方案数。答案为:
a n s = ∑ i = 0 m − 1 f n , i ans=\sum_{i=0}^{m-1} f_{n,i}
an s = i = 0 ∑ m − 1 f n , i
设 g i , j g_{i,j} g i , j 表示匹配到第 i i i 位时加入一个数字后匹配到第 j j j 位的方案数,可以用 KMP 求出。
f i , j = ∑ k = 0 m − 1 f i − 1 , k × g k , j f_{i,j}=\sum_{k=0}^{m-1}f_{i-1,k}\times g_{k,j}
f i , j = k = 0 ∑ m − 1 f i − 1 , k × g k , j
注意到这是矩阵乘法的形式,矩阵快速幂 O ( m 3 log n ) O(m^3\log n) O ( m 3 log n ) 。
在转化成矩阵的过程中,f i , j f_{i,j} f i , j 被转化为一个一行 m − 1 m-1 m − 1 列的矩阵。
code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 #include <cstdio> #include <cstring> template <typename T>inline void read (T &x) { bool f=0 ;x=0 ;char ch=getchar (); while (ch<'0' ||ch>'9' ){ if (ch=='-' ) f=1 ; ch=getchar (); } while (ch>='0' &&ch<='9' ){ x=(x<<1 )+(x<<3 )+(ch^48 ); ch=getchar (); } if (f) x=~x+1 ; } template <typename T,typename ...Args>void read (T &x,Args &...args) {read (x);read (args...);}int n,m,mod,nxt[25 ],ans;struct Matrix { int a[20 ][20 ]; Matrix (){memset (a,0 ,sizeof (a));} Matrix operator *(const Matrix &x)const { Matrix res; for (int k=0 ;k<m;k++) for (int i=0 ;i<m;i++) for (int j=0 ;j<m;j++) res.a[i][j]=(res.a[i][j]+a[i][k]*x.a[k][j]%mod)%mod; return res; } }base,f,g; Matrix qpow (Matrix a,int b) { Matrix res=base; while (b){ if (b&1 ) res=res*a; a=a*a; b>>=1 ; } return res; } char s[25 ];int main () { read (n,m,mod); for (int i=0 ;i<m;i++) base.a[i][i]=1 ; scanf ("%s" ,s+1 ); int j=0 ; for (int i=2 ;i<=m;i++){ while (j&&s[j+1 ]!=s[i]) j=nxt[j]; if (s[j+1 ]==s[i]) j++; nxt[i]=j; } for (int i=0 ;i<m;i++) for (char c='0' ;c<='9' ;c++){ j=i; while (j&&s[j+1 ]!=c) j=nxt[j]; if (s[j+1 ]==c) j++; g.a[i][j]=(g.a[i][j]+1 )%mod; } f.a[0 ][0 ]=1 ; f=f*qpow (g,n); for (int i=0 ;i<m;i++) ans=(ans+f.a[0 ][i])%mod; printf ("%d\n" ,ans); return 0 ; }
Manacher
感觉自己没理解 Manacher 啊,改天重学一次 。
由于只需要找长度为奇数的回文串,无需在字符间插入 #
,直接跑 Manacher,开差分数组 s u m sum s u m ,表示当前长度的回文串数量。当位置 i i i 的最长回文半径为 p i p_i p i 时,容易想到半径长度小于等于 p i p_i p i 的都是回文串,则我们只需在差分数组 s u m 1 sum_1 s u m 1 处加 1 1 1 ,s u m p i × 2 sum_{p_i\times 2} s u m p i × 2 处减 1 1 1 就行了。最后统计答案时要用快速幂。
code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 #include <cstdio> #include <algorithm> using namespace std;typedef long long ll;constexpr int N=1e6 +10 ,mod=19930726 ;int qpow (ll a,int b) { a%=mod; int res=1 ; while (b){ if (b&1 ) res=res*a%mod; a=a*a%mod; b>>=1 ; } return res; } int n,p[N];ll k,sum[N],ans; char s[N];int main () { scanf ("%d %lld" ,&n,&k); scanf ("%s" ,s+1 ); s[0 ]='#' ; for (int i=1 ,r=0 ,mid=0 ;i<=n;i++){ if (i<=r) p[i]=min (p[mid*2 -i],r-i+1 ); while (s[i-p[i]]==s[i+p[i]]) ++p[i]; if (p[i]+i>r) r=p[i]+i-1 ,mid=i; ++sum[1 ],--sum[p[i]*2 ]; } for (int i=1 ;i<=n;i++) sum[i]+=sum[i-1 ]; int tot=n; ans=1 ; if (tot%2 ==0 ) --tot; while (k&&tot>0 ){ ans=ans*qpow (tot,min (sum[tot],k))%mod; k=max (k-sum[tot],0ll ); tot-=2 ; } if (k) printf ("-1\n" ); else printf ("%lld\n" ,ans); return 0 ; }
在跑 Manacher 同时维护以每个点开头和结尾的最长回文串长度。仅 Manacher 求解的答案不完全,需要再递推地扫一遍,最后枚举中点即可。
code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 #include <cstdio> #include <cstring> #include <algorithm> using namespace std;constexpr int N=1e5 +10 ;char t[N],s[N<<1 ];int len0,len,p[N<<1 ],a[N<<1 ],b[N<<1 ],ans;int main () { scanf ("%s" ,t+1 ); len0=strlen (t+1 ); s[0 ]=s[1 ]='#' ; len=1 ; for (int i=1 ;i<=len0;i++){ s[++len]=t[i]; s[++len]='#' ; } for (int i=1 ,r=0 ,mid=0 ;i<=len;i++){ int j=2 *mid-i; if (i<=r) p[i]=min (r-i+1 ,p[j]); else p[i]=1 ; while (s[i-p[i]]==s[i+p[i]]) ++p[i]; if (i+p[i]-1 >r){ r=i+p[i]-1 ; mid=i; } a[i+p[i]-1 ]=max (a[i+p[i]-1 ],p[i]-1 ); b[i-p[i]+1 ]=max (b[i-p[i]+1 ],p[i]-1 ); } for (int i=len;i>=1 ;i-=2 ) a[i]=max (a[i],a[i+2 ]-2 ); for (int i=1 ;i<=len;i+=2 ) b[i]=max (b[i],b[i-2 ]-2 ); for (int i=1 ;i<=len;i+=2 ) if (a[i]&&b[i]) ans=max (ans,a[i]+b[i]); printf ("%d\n" ,ans); return 0 ; }
Day4
搜索
搜索题真是无聊死了,就少放几道吧……
n n n 遍 BFS 求全源最短路,之后考虑枚举。将 5 5 5 个点记作 1 , 2 , 3 , 4 , 5 1,2,3,4,5 1 , 2 , 3 , 4 , 5 ,则枚举 3 , 4 3,4 3 , 4 点,判断 2 , 5 2,5 2 , 5 是否与已选的点重复。可以预处理出每个能同时到达 1 1 1 点和另一个点的前三大点,因为它最多只有可能与 3 , 4 3,4 3 , 4 中的一个和 2 , 5 2,5 2 , 5 中的一个重合。总体时间复杂度 O ( n m + n 2 ) O(nm+n^2) O ( nm + n 2 ) 。
code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 #include <cstdio> #include <vector> #include <queue> #include <cstring> #include <set> using namespace std;template <typename T>inline void read (T &x) { bool f=0 ;x=0 ;char ch=getchar (); while (ch<'0' ||ch>'9' ){ if (ch=='-' ) f=1 ; ch=getchar (); } while (ch>='0' &&ch<='9' ){ x=(x<<1 )+(x<<3 )+(ch^48 ); ch=getchar (); } if (f) x=~x+1 ; } template <typename T,typename ...Args>void read (T &x,Args &...args) {read (x);read (args...);}typedef long long ll;constexpr int N=2510 ;int n,m,k,dis[N][N];ll ans,val[N]; vector<int > e[N]; queue<int > q; struct node { int id; node (int id):id (id){} bool operator <(const node &x)const { return val[id]!=val[x.id]?val[id]<val[x.id]:id<x.id; } }; set<node> st[N]; int main () { read (n,m,k);++k; for (int i=2 ;i<=n;i++) read (val[i]); for (int i=1 ,u,v;i<=m;i++){ read (u,v); e[u].push_back (v); e[v].push_back (u); } memset (dis,0x3f ,sizeof (dis)); for (int i=1 ;i<=n;i++){ dis[i][i]=0 ; q.push (i); while (!q.empty ()){ int u=q.front (); q.pop (); for (int v:e[u]) if (dis[i][u]+1 <dis[i][v]&&dis[i][u]+1 <=k){ dis[i][v]=dis[i][u]+1 ; q.push (v); } } } for (int i=2 ;i<=n;i++) for (int j=2 ;j<=n;j++){ if (i==j) continue ; if (dis[i][j]<=k&&dis[1 ][j]<=k) st[i].insert (node (j)); if (st[i].size ()>3 ) st[i].erase (st[i].begin ()); } for (int b=2 ;b<=n;b++) for (int c=2 ;c<=n;c++){ if (b==c||dis[b][c]>k) continue ; for (auto temp1:st[b]){ int a=temp1. id; if (a==b||a==c) continue ; for (auto temp2:st[c]){ int d=temp2. id; if (d==b||d==c||a==d) continue ; ans=max (ans,val[a]+val[b]+val[c]+val[d]); } } } printf ("%lld\n" ,ans); return 0 ; }
IDA* 板题。先离散化,估价函数为相邻差不为 1 1 1 的个数。
code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 #include <cstdio> #include <algorithm> using namespace std;template <typename T>inline void read (T &x) { bool f=0 ;x=0 ;char ch=getchar (); while (ch<'0' ||ch>'9' ){ if (ch=='-' ) f=1 ; ch=getchar (); } while (ch>='0' &&ch<='9' ){ x=(x<<1 )+(x<<3 )+(ch^48 ); ch=getchar (); } if (f) x=~x+1 ; } template <typename T,typename ...Args>void read (T &x,Args &...args) {read (x);read (args...);}constexpr int N=18 ;int n,a[N],b[N],lim;inline int h () { int res=0 ; for (int i=1 ;i<=n;i++) res+=(abs (a[i+1 ]-a[i])!=1 ); return res; } bool dfs (int cnt,int pre) { int temp=h (); if (temp==0 ) return 1 ; if (cnt+temp>lim) return 0 ; bool f=0 ; for (int i=1 ;i<=n;i++){ if (i==pre) continue ; reverse (a+1 ,a+1 +i); f|=dfs (cnt+1 ,i); reverse (a+1 ,a+1 +i); } return f; } int main () { read (n); for (int i=1 ;i<=n;i++) read (a[i]),b[i]=a[i]; sort (b+1 ,b+1 +n); for (int i=1 ;i<=n;i++) a[i]=lower_bound (b+1 ,b+1 +n,a[i])-b; a[n+1 ]=n+1 ; lim=1 ; while (1 ){ if (dfs (0 ,0 )){ printf ("%d\n" ,lim); break ; } ++lim; } return 0 ; }
容易发现,p i p_i p i 越小,方案数越多。n = 16 n=16 n = 16 时不可接受,考虑 Meet in the middle。最坏情况下前 8 8 8 个质数 2 , 3 , 5 , 7 , 11 , 13 , 17 , 19 2,3,5,7,11,13,17,19 2 , 3 , 5 , 7 , 11 , 13 , 17 , 19 ,在 10 18 10^{18} 1 0 18 内能组成 7039193 7039193 7039193 种数字。实际实现时可以将数的分配得更加均匀以降低复杂度。之后二分答案+双指针求解即可。
code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 #include <cstdio> #include <algorithm> using namespace std;template <typename T>inline void read (T &x) { bool f=0 ;x=0 ;char ch=getchar (); while (ch<'0' ||ch>'9' ){ if (ch=='-' ) f=1 ; ch=getchar (); } while (ch>='0' &&ch<='9' ){ x=(x<<1 )+(x<<3 )+(ch^48 ); ch=getchar (); } if (f) x=~x+1 ; } template <typename T,typename ...Args>void read (T &x,Args &...args) {read (x);read (args...);}typedef long long ll;constexpr ll lim=1e18 ,N=7039192 +10 ;int n,p[20 ],k,tota,totb;ll a[N],b[N]; void dfs1 (int x,ll num) { if (x>n) return ; dfs1 (x+2 ,num); for (ll i=p[x];;i*=p[x]){ if (lim/i<num) break ; a[++tota]=num*i; dfs1 (x+2 ,num*i); } } void dfs2 (int x,ll num) { if (x>n) return ; dfs2 (x+2 ,num); for (ll i=p[x];;i*=p[x]){ if (lim/i<num) break ; b[++totb]=num*i; dfs2 (x+2 ,num*i); } } inline bool check (ll x) { ll cnt=0 ; for (int i=1 ,j=totb;i<=tota;i++){ while (j&&x/a[i]<b[j]) --j; cnt+=j; if (j==0 ) break ; } return cnt<k; } int main () { read (n); for (int i=1 ;i<=n;i++) read (p[i]); sort (p+1 ,p+1 +n); a[++tota]=b[++totb]=1 ; dfs1 (1 ,1 ); dfs2 (2 ,1 ); sort (a+1 ,a+1 +tota); sort (b+1 ,b+1 +totb); read (k); ll l=1 ,r=1e18 ; while (l<=r){ ll mid=(l+r)>>1 ; if (check (mid)) l=mid+1 ; else r=mid-1 ; } printf ("%lld\n" ,l); return 0 ; }
Meet in the middle,开一个 std::map
记录状态对应的最小操作次数,按照补集相加。
code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 #include <cstdio> #include <map> using namespace std;template <typename T>inline void read (T &x) { bool f=0 ;x=0 ;char ch=getchar (); while (ch<'0' ||ch>'9' ){ if (ch=='-' ) f=1 ; ch=getchar (); } while (ch>='0' &&ch<='9' ){ x=(x<<1 )+(x<<3 )+(ch^48 ); ch=getchar (); } if (f) x=~x+1 ; } template <typename T,typename ...Args>void read (T &x,Args &...args) {read (x);read (args...);}typedef long long ll;constexpr int N=40 ;int n,m,ans;ll a[N]; map<ll,int > mp; int main () { read (n,m); for (int i=0 ;i<n;i++) a[i]=1ll <<i; for (int i=1 ,u,v;i<=m;i++){ read (u,v); --u,--v; a[u]|=1ll <<v; a[v]|=1ll <<u; } ans=n+1 ; for (int i=0 ;i<(1 <<(n>>1 ));i++){ ll temp=0 ; for (int j=0 ;j<(n>>1 );j++) if ((i>>j)&1 ) temp^=a[j]; if (!mp.count (temp)) mp[temp]=__builtin_popcount(i); else mp[temp]=min (mp[temp],__builtin_popcount(i)); } for (int i=0 ;i<(1 <<(n-(n>>1 )));i++){ ll temp=0 ; for (int j=0 ;j<n-(n>>1 );j++) if ((i>>j)&1 ) temp^=a[(n>>1 )+j]; if (mp.count (((1ll <<n)-1 )^temp)) ans=min (ans,__builtin_popcount(i)+mp[((1ll <<n)-1 )^temp]); } printf ("%d\n" ,ans); return 0 ; }
坏题。
考虑 A*。容易想到 4 4 4 进制状压,则状态 x x x 的估价 h ( x ) h(x) h ( x ) 应为当前已经旋转的次数与所有不为 1 1 1 的旋钮旋到 1 1 1 所需总步数除以 2 2 2 的和。因为最好情况是旋一个按钮时另一个正好也到了 1 1 1 。每个状态记录一个 p r e pre p re 表示是从哪个状态转移过来的以输出方案。
code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 #include <cstdio> #include <queue> using namespace std;template <typename T>inline void read (T &x) { bool f=0 ;x=0 ;char ch=getchar (); while (ch<'0' ||ch>'9' ){ if (ch=='-' ) f=1 ; ch=getchar (); } while (ch>='0' &&ch<='9' ){ x=(x<<1 )+(x<<3 )+(ch^48 ); ch=getchar (); } if (f) x=~x+1 ; } template <typename T,typename ...Args>void read (T &x,Args &...args) {read (x);read (args...);}constexpr int N=(1 <<24 )+10 ;int st,a[15 ][4 ],b[N],pre[N],step[N];bool vis[N];void write (int x) { if (x==st) return ; write (pre[x]); printf ("%d " ,b[x]); } inline int h (int x) { int res=0 ; for (int i=0 ;i<12 ;i++){ int temp=(x>>(i<<1 ))&3 ; res+=temp?4 -temp:0 ; } return res>>1 ; } struct node { int x,h; node (int x,int h):x (x),h (h){} bool operator <(const node &a)const {return h>a.h;} }; priority_queue<node> q; int main () { for (int i=0 ,x;i<12 ;i++){ read (x); st|=(x-1 )<<(i<<1 ); for (int j=0 ;j<4 ;j++) read (a[i][j]),--a[i][j]; } q.push (node (st,h (st))); vis[st]=1 ; while (!q.empty ()){ int x=q.top ().x; q.pop (); if (x==0 ) break ; for (int i=0 ;i<12 ;i++){ int pos1=i<<1 ,temp1=(x>>pos1)&3 ,j=a[i][temp1]; int pos2=j<<1 ,temp2=(x>>pos2)&3 ,res=x; res&=~(3 <<pos1); res&=~(3 <<pos2); res|=((temp1+1 )&3 )<<pos1; res|=((temp2+1 )&3 )<<pos2; if (!vis[res]){ vis[res]=1 ; step[res]=step[x]+1 ; pre[res]=x,b[res]=i+1 ; q.push (node (res,step[res]+h (res))); } } } printf ("%d\n" ,step[0 ]); write (0 ); return 0 ; }
Day5
最小生成树
最优情况一定是走最大生成树上的边,所以先 Kruskal 求最大生成树,接下来求树上两点间路径权值最小值,可以边权下放点权上树剖+线段树或倍增。另外,图不保证连通,实际上要对森林做操作。
code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 #include <cstdio> #include <algorithm> #include <vector> #include <cstring> using namespace std;template <typename T>inline void read (T &x) { bool f=0 ;x=0 ;char ch=getchar (); while (ch<'0' ||ch>'9' ){ if (ch=='-' ) f=1 ; ch=getchar (); } while (ch>='0' &&ch<='9' ){ x=(x<<1 )+(x<<3 )+(ch^48 ); ch=getchar (); } if (f) x=~x+1 ; } template <typename T,typename ...Args>void read (T &x,Args &...args) {read (x);read (args...);}constexpr int N=1e4 +10 ,M=5e4 +10 ;int n,m,q,boss[N],cnt;int find (int x) {return boss[x]==x?x:boss[x]=find (boss[x]);}struct Edge { int u,v,w; bool operator <(const Edge &x)const {return w>x.w;} }edge[M]; struct Edge1 {int v,w;};vector<Edge1> e[N]; void kruskal () { for (int i=1 ;i<=n;i++) boss[i]=i; sort (edge+1 ,edge+1 +m); for (int i=1 ;i<=m&&cnt<n-1 ;i++){ int u=find (edge[i].u),v=find (edge[i].v); if (u==v) continue ; boss[u]=v; ++cnt; u=edge[i].u,v=edge[i].v; int w=edge[i].w; e[u].push_back ({v,w}); e[v].push_back ({u,w}); } } int dfn[N],dfncnt,son[N],fa[N],siz[N],top[N],dep[N],w[N],P,tree[N<<2 ];void dfs1 (int u,int f) { siz[u]=1 ; fa[u]=f; dep[u]=dep[f]+1 ; for (auto t:e[u]){ int v=t.v; if (v==f) continue ; w[v]=t.w; dfs1 (v,u); siz[u]+=siz[v]; if (siz[v]>siz[son[u]]) son[u]=v; } } void dfs2 (int u,int topf) { top[u]=topf; dfn[u]=++dfncnt; if (son[u]) dfs2 (son[u],topf); for (auto t:e[u]){ int v=t.v; if (v==fa[u]||v==son[u]) continue ; dfs2 (v,v); } } #define ls(x) (x<<1) #define rs(x) (x<<1|1) void build () { for (int i=1 ;i<=n;i++) if (!siz[i]) w[i]=2e9 ,dfs1 (i,0 ); for (int i=1 ;i<=n;i++) if (!dfn[i]) dfs2 (i,i); P=1 ; while (P<=n+1 ) P<<=1 ; memset (tree,0x3f ,sizeof (tree)); for (int i=1 ;i<=n;i++) tree[P+dfn[i]]=w[i]; for (int i=P-1 ;i;i--) tree[i]=min (tree[ls (i)],tree[rs (i)]); } int query (int l,int r) { l+=P-1 ,r+=P+1 ; int res=2e9 ; while (l^1 ^r){ if (~l&1 ) res=min (tree[l^1 ],res); if (r&1 ) res=min (tree[r^1 ],res); l>>=1 ,r>>=1 ; } return res; } int ask (int x,int y) { if (find (x)!=find (y)) return -1 ; int res=2e9 ; while (top[x]!=top[y]){ if (dep[top[x]]<dep[top[y]]) swap (x,y); res=min (res,query (dfn[top[x]],dfn[x])); x=fa[top[x]]; } if (dep[x]>dep[y]) swap (x,y); if (x!=y) res=min (res,query (dfn[x]+1 ,dfn[y])); return res; } int main () { read (n,m); for (int i=1 ,u,v,w;i<=m;i++){ read (u,v,w); edge[i]={u,v,w}; } kruskal (); build (); read (q); int x,y; while (q--){ read (x,y); printf ("%d\n" ,ask (x,y)); } return 0 ; }
跟上一道题一个套路。考虑枚举替换边的过程,首先加一条边形成环,再从环上删去最大的一条小于加入边边权的边。线段树维护区间最大值和区间严格次大值即可。
code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 #include <cstdio> #include <vector> #include <algorithm> using namespace std;template <typename T>inline void read (T &x) { bool f=0 ;x=0 ;char ch=getchar (); while (ch<'0' ||ch>'9' ){ if (ch=='-' ) f=1 ; ch=getchar (); } while (ch>='0' &&ch<='9' ){ x=(x<<1 )+(x<<3 )+(ch^48 ); ch=getchar (); } if (f) x=~x+1 ; } template <typename T,typename ...Args>void read (T &x,Args &...args) {read (x);read (args...);}typedef long long ll;constexpr int N=1e5 +10 ,M=3e5 +10 ;int n,m,boss[N],cnt;struct node { int maxn,se; node (int maxn=-1 ,int se=-1 ):maxn (maxn),se (se){} node operator +(const node &x)const { node res; res.maxn=max (maxn,x.maxn); res.se=max (se,x.se); if (maxn!=x.maxn) res.se=max (res.se,min (maxn,x.maxn)); return res; } }; int P;node tree[N<<2 ]; node query (int l,int r) { l+=P-1 ,r+=P+1 ; node res; while (l^1 ^r){ if (~l&1 ) res=res+tree[l^1 ]; if (r&1 ) res=res+tree[r^1 ]; l>>=1 ,r>>=1 ; } return res; } struct Edge { int u,v,w; bool operator <(const Edge &x)const {return w<x.w;} }edge[M]; struct Edge1 {int v,w;};vector<Edge1> e[N]; ll ans,minval; int dfn[N],top[N],dfncnt,dep[N],fa[N],siz[N],son[N],w[N];bool vis[N];int find (int x) {return boss[x]==x?x:boss[x]=find (boss[x]);}void dfs1 (int u,int f) { dep[u]=dep[f]+1 ; fa[u]=f; siz[u]=1 ; for (auto t:e[u]){ int v=t.v; if (v==f) continue ; w[v]=t.w; dfs1 (v,u); siz[u]+=siz[v]; if (siz[v]>siz[son[u]]) son[u]=v; } } void dfs2 (int u,int topf) { top[u]=topf; dfn[u]=++dfncnt; if (son[u]) dfs2 (son[u],topf); for (auto t:e[u]){ int v=t.v; if (v==fa[u]||v==son[u]) continue ; dfs2 (v,v); } } node ask (int x,int y) { node res; while (top[x]!=top[y]){ if (dep[top[x]]<dep[top[y]]) swap (x,y); res=res+query (dfn[top[x]],dfn[x]); x=fa[top[x]]; } if (dep[x]>dep[y]) swap (x,y); if (x!=y) res=res+query (dfn[x]+1 ,dfn[y]); return res; } int main () { read (n,m); for (int i=1 ,u,v,w;i<=m;i++){ read (u,v,w); edge[i]={u,v,w}; } sort (edge+1 ,edge+1 +m); for (int i=1 ;i<=n;i++) boss[i]=i; for (int i=1 ;i<=m&&cnt<n-1 ;i++){ int u=find (edge[i].u),v=find (edge[i].v); if (u==v) continue ; boss[u]=v; vis[i]=1 ; u=edge[i].u,v=edge[i].v; int w=edge[i].w; minval+=w; e[u].push_back ({v,w}); e[v].push_back ({u,w}); if (++cnt==n-1 ) break ; } dfs1 (1 ,0 ); dfs2 (1 ,1 ); P=1 ; while (P<=n+1 ) P<<=1 ; for (int i=1 ;i<=n;i++) tree[P+dfn[i]]=w[i]; for (int i=P-1 ;i;i--) tree[i]=tree[i<<1 ]+tree[i<<1 |1 ]; ans=1e18 ; for (int i=1 ;i<=m;i++){ if (vis[i]) continue ; int x=edge[i].u,y=edge[i].v,w=edge[i].w; if (x==y) continue ; node temp=ask (x,y); if (temp.maxn==w) ans=min (ans,minval-temp.se+w); else ans=min (ans,minval-temp.maxn+w); } printf ("%lld\n" ,ans); return 0 ; }
Kruskal 重构树的在线做法还是太吃操作了,我们直接离线。容易想到将边权和询问的 x x x 升序排序后动态加边,查询第 k k k 大直接线段树上二分,加边用并查集+线段树合并维护。
code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 #include <cstdio> #include <algorithm> #include <vector> using namespace std;template <typename T>inline void read (T &x) { bool f=0 ;x=0 ;char ch=getchar (); while (ch<'0' ||ch>'9' ){ if (ch=='-' ) f=1 ; ch=getchar (); } while (ch>='0' &&ch<='9' ){ x=(x<<1 )+(x<<3 )+(ch^48 ); ch=getchar (); } if (f) x=~x+1 ; } template <typename T,typename ...Args>void read (T &x,Args &...args) {read (x);read (args...);}constexpr int N=1e5 +10 ,M=5e5 +10 ;int n,m,q,h[N],b[N],len,tot,root[N],fa[N],now,ans[M];int find (int x) {return fa[x]==x?x:fa[x]=find (fa[x]);}struct edge { int u,v,w; bool operator <(const edge &x)const {return w<x.w;} }e[M]; struct Query { int v,x,k,id; bool operator <(const Query &a)const {return x<a.x;} }qu[M]; struct Tree { int ls,rs,sum; }tree[N<<5 ]; inline void push_up (int u) { tree[u].sum=tree[tree[u].ls].sum+tree[tree[u].rs].sum; } void modify (int &u,int l,int r,int x) { if (!u) u=++tot; if (l==r){ ++tree[u].sum; return ; } int mid=(l+r)>>1 ; if (x<=mid) modify (tree[u].ls,l,mid,x); else modify (tree[u].rs,mid+1 ,r,x); push_up (u); } int merge (int a,int b,int l,int r) { if (!a||!b) return a|b; if (l==r){ tree[a].sum+=tree[b].sum; return a; } int mid=(l+r)>>1 ; tree[a].ls=merge (tree[a].ls,tree[b].ls,l,mid); tree[a].rs=merge (tree[a].rs,tree[b].rs,mid+1 ,r); push_up (a); return a; } int query (int u,int l,int r,int k) { if (l==r) return l; int mid=(l+r)>>1 ; if (k<=tree[tree[u].ls].sum) return query (tree[u].ls,l,mid,k); else return query (tree[u].rs,mid+1 ,r,k-tree[tree[u].ls].sum); } int main () { read (n,m,q); for (int i=1 ;i<=n;i++) fa[i]=i; for (int i=1 ;i<=n;i++){ read (h[i]); b[i]=h[i]; } sort (b+1 ,b+1 +n); len=unique (b+1 ,b+1 +n)-b-1 ; for (int i=1 ;i<=n;i++){ int x=lower_bound (b+1 ,b+1 +len,h[i])-b; modify (root[i],1 ,len,x); } for (int i=1 ,u,v,w;i<=m;i++){ read (u,v,w); e[i]={u,v,w}; } sort (e+1 ,e+1 +m); for (int i=1 ,v,x,k;i<=q;i++){ read (v,x,k); qu[i]={v,x,k,i}; } sort (qu+1 ,qu+1 +q); for (int i=1 ;i<=q;i++){ int x=qu[i].x,k=qu[i].k,id=qu[i].id; while (e[now+1 ].w<=x&&now<m){ ++now; int u=find (e[now].u),v=find (e[now].v); if (u==v) continue ; fa[u]=v; root[v]=merge (root[v],root[u],1 ,len); } int v=find (qu[i].v); if (tree[root[v]].sum<k) ans[id]=-1 ; else ans[id]=b[query (root[v],1 ,len,tree[root[v]].sum-k+1 )]; } for (int i=1 ;i<=q;i++) printf ("%d\n" ,ans[i]); return 0 ; }
最短路
没做。
拓扑排序
容易发现,半连通子图就是若干相连的强连通分量,则找最大半连通子图转化为缩点后在 DAG 上找最长链+计数。注意去重。
code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 #include <cstdio> #include <stack> #include <vector> #include <set> #include <queue> using namespace std;template <typename T>inline void read (T &x) { bool f=0 ;x=0 ;char ch=getchar (); while (ch<'0' ||ch>'9' ){ if (ch=='-' ) f=1 ; ch=getchar (); } while (ch>='0' &&ch<='9' ){ x=(x<<1 )+(x<<3 )+(ch^48 ); ch=getchar (); } if (f) x=~x+1 ; } template <typename T,typename ...Args>void read (T &x,Args &...args) {read (x);read (args...);}constexpr int N=1e5 +10 ,M=1e6 +10 ;int n,m,mod,head[N],tot_edge;struct edge {int to,nxt;}e1[M];inline void add_edge (int u,int v) { e1[++tot_edge].to=v; e1[tot_edge].nxt=head[u]; head[u]=tot_edge; } stack<int > st; bool instack[N];int dfn[N],low[N],dfncnt,scccnt,scc[N],w[N];void tarjan (int u) { dfn[u]=low[u]=++dfncnt; instack[u]=1 ; st.push (u); for (int i=head[u];i;i=e1[i].nxt){ int v=e1[i].to; if (!dfn[v]){ tarjan (v); low[u]=min (low[u],low[v]); } else if (instack[v]) low[u]=min (low[u],dfn[v]); } if (dfn[u]==low[u]){ ++scccnt; while (1 ){ int v=st.top (); st.pop (); instack[v]=0 ; scc[v]=scccnt; ++w[scccnt]; if (u==v) break ; } } } vector<int > e2[N]; int indegree[N],dp[N],dis[N],ans1,ans2;set<pair<int ,int >> p; queue<int > q; int main () { read (n,m,mod); for (int i=1 ,u,v;i<=m;i++){ read (u,v); add_edge (u,v); } for (int i=1 ;i<=n;i++) if (!dfn[i]) tarjan (i); for (int u=1 ;u<=n;u++) for (int i=head[u];i;i=e1[i].nxt){ int v=e1[i].to; if (scc[u]==scc[v]) continue ; if (p.find (make_pair (scc[u],scc[v]))!=p.end ()) continue ; p.insert (make_pair (scc[u],scc[v])); e2[scc[u]].push_back (scc[v]); ++indegree[scc[v]]; } for (int i=1 ;i<=scccnt;i++) if (!indegree[i]){ q.push (i); dis[i]=w[i]; dp[i]=1 ; } while (!q.empty ()){ int u=q.front (); q.pop (); for (int v:e2[u]){ if (dis[v]<dis[u]+w[v]){ dis[v]=dis[u]+w[v]; dp[v]=dp[u]; } else if (dis[v]==dis[u]+w[v]) dp[v]=(dp[u]+dp[v])%mod; if (!--indegree[v]) q.push (v); } } for (int i=1 ;i<=scccnt;i++) ans1=max (ans1,dis[i]); for (int i=1 ;i<=scccnt;i++) if (dis[i]==ans1) ans2=(ans2+dp[i])%mod; printf ("%d\n%d\n" ,ans1,ans2); return 0 ; }
Day6
二分图
连接每个 a i a_i a i 和 b i b_i b i ,问题变为了二分图上找最小点覆盖。直接跑二分图最大匹配即可。
Kőnig 定理:二分图中,最小点覆盖中的顶点数量等于最大匹配中的边数量。
code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 #include <cstdio> #include <vector> #include <cstring> using namespace std;template <typename T>inline void read (T &x) { bool f=0 ;x=0 ;char ch=getchar (); while (ch<'0' ||ch>'9' ){ if (ch=='-' ) f=1 ; ch=getchar (); } while (ch>='0' &&ch<='9' ){ x=(x<<1 )+(x<<3 )+(ch^48 ); ch=getchar (); } if (f) x=~x+1 ; } template <typename T,typename ...Args>void read (T &x,Args &...args) {read (x);read (args...);}constexpr int N=1010 ;int n,m,k,ans,match[N];bool vis[N];vector<int > e[N]; bool dfs (int u) { for (int v:e[u]) if (!vis[v]){ vis[v]=1 ; if (!match[v]||dfs (match[v])){ match[v]=u; return 1 ; } } return 0 ; } int main () { while (1 ){ for (int i=1 ;i<=n;i++) e[i].clear (); memset (match,0 ,sizeof (int )*(n+10 )); read (n); if (!n) return 0 ; read (m,k); for (int i=1 ,u,v,a;i<=k;i++){ read (a,u,v); e[u].push_back (v); } ans=0 ; for (int i=1 ;i<=n;i++){ memset (vis,0 ,sizeof (bool )*(n+10 )); if (dfs (i)) ++ans; } printf ("%d\n" ,ans); } return 0 ; }
贪心地考虑,极长地放木板一定优。将所有横着的和竖着的木板编号,相交的连边,之后二分图最大匹配。
code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 #include <iostream> #include <vector> #include <cstring> using namespace std;constexpr int N=55 ;int r,c,tota,totb,a[N][N],b[N][N],match[N*N],ans;char mp[N][N];bool vis[N*N];vector<int > e[N*N]; bool dfs (int u) { for (int v:e[u]) if (!vis[v]){ vis[v]=1 ; if (!match[v]||dfs (match[v])){ match[v]=u; return 1 ; } } return 0 ; } int main () { ios::sync_with_stdio (0 ),cin.tie (0 ),cout.tie (0 ); cin>>r>>c; for (int i=1 ;i<=r;i++) for (int j=1 ;j<=c;j++){ cin>>mp[i][j]; if (mp[i][j]=='*' ){ if (mp[i][j-1 ]=='*' ) a[i][j]=a[i][j-1 ]; else a[i][j]=++tota; if (mp[i-1 ][j]=='*' ) b[i][j]=b[i-1 ][j]; else b[i][j]=++totb; if (a[i][j]&&b[i][j]) e[a[i][j]].push_back (b[i][j]); } } for (int i=1 ;i<=tota;i++){ memset (vis,0 ,sizeof (vis)); if (dfs (i)) ++ans; } cout<<ans<<'\n' ; return 0 ; }
最大独立集与最小点覆盖之和为顶点数目,这个推论适用于一般图。
code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 #include <iostream> #include <vector> #include <cstring> using namespace std;constexpr int N=510 ;int t,n,match[N],ans,cnta,cntb;bool vis[N];struct student { int h; char sex; string music,sport; }a[N],b[N]; vector<int > e[N]; bool dfs (int u) { for (int v:e[u]) if (!vis[v]){ vis[v]=1 ; if (!match[v]||dfs (match[v])){ match[v]=u; return 1 ; } } return 0 ; } int main () { ios::sync_with_stdio (0 ),cin.tie (0 ),cout.tie (0 ); cin>>t; while (t--){ for (int i=1 ;i<=n;i++) e[i].clear (); memset (match,0 ,sizeof (int )*(n+10 )); cin>>n; for (int i=1 ;i<=n;i++){ cin>>a[i].h>>a[i].sex>>a[i].music>>a[i].sport; for (int j=1 ;j<i;j++){ if (abs (a[i].h-a[j].h)>40 ||a[i].sex==a[j].sex|| a[i].music!=a[j].music||a[i].sport==a[j].sport) continue ; if (a[i].sex=='M' ) e[i].push_back (j); else e[j].push_back (i); } } ans=0 ; for (int i=1 ;i<=n;i++){ memset (vis,0 ,sizeof (bool )*(n+10 )); if (dfs (i)) ++ans; } printf ("%d\n" ,n-ans); } return 0 ; }
欧拉路径
没做。
连通分量
没做。
参考资料:
https://oi-wiki.org/graph/graph-matching/bigraph-match/