[冬令營模擬]wzj的題目#1
T1 少膜一個,T3 暴力寫掛
強勢 rank1 -> rank2
一場比賽兩道線段樹分治,給力
T1 password
給你 m 個禁止字串,求長度為 n 的所有字串中至少包含這些禁止字串各一次的字串數量
$n \leq 10^9,m \leq 4,\sum len \leq 50$
sol:容斥一下就變成了“m 個禁止字串,一個都不出現的字串數量”
這個可以轉化成從 init 節點走 x 步走不到任意一個節點的方案數
矩陣加速即可
需要注意的是狀態壓縮被卡了,這種狀壓 + 矩乘可以用容斥來把矩陣變小
比賽的時候因為少膜了一下,掛了一個點
#include<bits/stdc++.h> #define int long long #define LL long long using namespace std; inline int read() { int x = 0,f = 1;char ch = getchar(); for(;!isdigit(ch);ch = getchar())if(ch == '-')f = -f; for(;isdigit(ch);ch = getchar())x = 10 * x + ch - '0'; returnT1x * f; } const int N = 150; const int A = 10; const int MOD = 998244353; typedef vector<int> vec; typedef vector<vec> mat; mat mul(mat &A, mat &B) { mat C(A.size(), vec(B[0].size())); for (int i = 0; i < A.size(); ++i) { for (int k = 0; k < B.size(); ++k) {for (int j = 0; j < B[0].size(); ++j) { C[i][j] = (C[i][j] + (LL)A[i][k] * B[k][j]) % MOD; } } } return C; } mat pow(mat A, int n) { mat B(A.size(), vec(A.size())); for (int i = 0; i < A.size(); ++i) B[i][i] = 1; while (n > 0) { if (n & 1) B = mul(B, A); A = mul(A, A); n >>= 1; } return B; } struct ACAutomata { int next[N][A], fail[N], end[N]; int root, L; int idx(char ch) { return ch - '0'; } int newNode() { for (int i = 0; i < A; ++i) next[L][i] = -1; end[L] = 0; return L++; } void init() { L = 0; root = newNode(); } void insert(char buf[]) { int len = strlen(buf); int now = root; for (int i = 0; i < len; ++i) { int ch = idx(buf[i]); if (next[now][ch] == -1) next[now][ch] = newNode(); now = next[now][ch]; } end[now]++; } void build() { queue<int> Q; fail[root] = root; for (int i = 0; i < A; ++i) { if (next[root][i] == -1) { next[root][i] = root; } else { fail[ next[root][i] ] = root; Q.push( next[root][i] ); } } while (!Q.empty()) { int now = Q.front(); Q.pop(); if (end[ fail[now] ]) end[now]++; for (int i = 0; i < A; ++i) { if (next[now][i] == -1) { next[now][i] = next[ fail[now] ][i]; } else { fail[ next[now][i] ] = next[ fail[now] ][i]; Q.push(next[now][i]); } } } } int query(int n) { mat F(L, vec(L)); for (int i = 0; i < L; ++i) { for (int j = 0; j < L; ++j) { F[i][j] = 0; } } for (int i = 0; i < L; ++i) { for (int j = 0; j < A; ++j) { int nt = next[i][j]; if (!end[nt]) F[i][nt]++; } } F = pow(F, n); int res = 0; for (int i = 0; i < L; ++i) { res = (res + F[0][i]) % MOD; } return res; } } ac; int m,n; char buf[20][60]; int skr(int x,int t) { int res = 1; while(t) { if(t & 1)res = res * x % MOD; x = x * x % MOD; t = t >> 1; }return res; } signed main() { freopen("password.in","r",stdin); freopen("password.out","w",stdout); m = read(),n = read(); int qwq = 0,qnq = skr(10,n); for(int i=1;i<=m;i++)cin>>buf[i]; for(int SS=1;SS<=(1<<m)-1;SS++) { int flg = __builtin_popcount(SS),S = (flg & 1) ? 1 : -1; ac.init(); memset(ac.next,-1,sizeof(ac.next)); memset(ac.fail,0,sizeof(ac.fail)); memset(ac.end,0,sizeof(ac.end)); for(int i=0;i<m;i++) if(SS & (1 << i))ac.insert(buf[i+1]); ac.build(); qwq += (S * ac.query(n)); } cout<<((((qnq-qwq) % MOD) + MOD) % MOD)<<endl; return 0; }
T2 paint
APIO2014 特別行動隊,但是所有東西都可以為負
額...就是你現在有 n 個人,每個人有權值,你可以把它們分成若干組,第 i 人只能跟 $[i-l,i+r]$ 這些人一組,一組的權值和為 $s$,一組的權值為 $ax^2+bx+c$ ,讓你最大化權值
sol:因為一個人只能由 $[i-l,i+r]$ 轉移過來,我們可以考慮這個人有貢獻的區間,那顯然是 $[i+l,min(i+r,n)]$ ,我們可以線上段樹這個區間上打上這個人的 tag
然後我們按線段樹的 dfs 序掃這個區間,因為這個人有貢獻的區間一定在這個人右邊,所以處理到這個區間的時候,所有打在上面的 tag 已經處理完畢了
於是我們可以把從這個葉子到根的 logn 層凸包並起來二分
然後就寫完了。。
#include<bits/stdc++.h> #define LL long long #define DB long double using namespace std; inline int read() { int x = 0,f = 1;char ch = getchar(); for(;!isdigit(ch);ch = getchar())if(ch == '-')f = -f; for(;isdigit(ch);ch = getchar())x = 10 * x + ch - '0'; return x * f; } const int maxn = 152600; const DB inf = (DB)1.0 / 0.0; int n,l,r; LL a,b,c; LL sum[maxn],f[maxn]; void force() { for(int i=1;i<=n;i++) { for(int j=max(i-r,0);j<=min(i-l,n);j++) f[i] = max(f[i],f[j] + a * (sum[i] - sum[j]) * (sum[i] - sum[j]) + b * (sum[i] - sum[j]) + c); } cout<<f[n]<<endl; } struct point { DB x,y; int id; point operator - (const point &b)const{return (point){x - b.x,y - b.y,id};} bool operator < (const point &b)const{return x < b.x;} DB operator * (const point &b)const{return x * b.y - y * b.x;} }ps[maxn]; vector<int> Items[maxn << 2],ch[maxn << 2]; DB dp[maxn],Sum[maxn]; int q[maxn],top; #define ls (x << 1) #define rs ((x << 1) | 1) void Insert(int x, int l, int r, int L, int R, int id) { if(L <= l && r <= R){Items[x].push_back(id);return;} int mid = (l + r) >> 1; if(L <= mid) Insert(ls,l,mid,L,R,id); if(R > mid) Insert(rs,mid + 1,r,L,R,id); } inline DB trans(int frm,int targ){return (DB)dp[frm] + (DB)a * (Sum[targ] - Sum[frm]) * (Sum[targ] - Sum[frm]) + (DB)b * (Sum[targ] - Sum[frm]) + c;} void CDQ(int x,int l,int r) { int tp = 0; for(int i=0;i<Items[x].size();i++) { int to = Items[x][i]; ps[++tp] = (point){Sum[to],dp[to] + (DB)a * Sum[to] * Sum[to] - (DB)b * Sum[to],to}; } if(tp) { sort(ps + 1,ps + tp + 1);top = 0; q[++top] = 1; for(;q[1] <= tp && ps[q[1]].y == -inf;++q[1]); for(int i=q[1]+1;i<=tp;i++) { if(ps[i].y == -inf) continue; for(;top > 1 && ((ps[q[top]] - ps[q[top-1]]) * (ps[i] - ps[q[top]])) >= 0;--top); q[++top] = i; } if(q[1] > tp)top = 0; for(;top && ps[q[top]].y == -inf;--top); if(top)for(int i=1;i<=top;i++)ch[x].push_back(ps[q[i]].id); } if(l == r) { int now = x; while(now) { int nl = 0,nr = ch[now].size() - 1; while(nl < nr) { int md = (nl + nr) >> 1; if(md < (ch[now].size() - 1) && trans(ch[now][md],l) <= trans(ch[now][md+1],l))nl = md + 1; else nr = md; } if(ch[now].size())dp[l] = max(dp[l],trans(ch[now][nl],l)); now = now >> 1; } return; } int mid = (l + r) >> 1; CDQ(ls,l,mid);CDQ(rs,mid + 1,r); } void solve() { for(int i=1;i<=n;i++)Sum[i] = sum[i]; for(int i=1;i<=n;i++)dp[i] = -inf; for(int i=0;i<=n;i++)Insert(1,0,n,i + l,min(i+r,n),i); //cout<<111<<endl; CDQ(1,0,n);LL ans = floor(dp[n]); printf("%lld\n",ans); //cout<<ret<<endl; } int main() { freopen("paint.in","r",stdin); freopen("paint.out","w",stdout); n = read();a = read();b = read();c = read();l = read();r = read(); for(int i=1;i<=n;i++)sum[i] = sum[i - 1] + read(); //if(n <= 1000)force(); //else solve(); }T2
T3 route
樹上找一條鏈,滿足
1.鏈上最長邊和最短邊的差不超過 m
2.鏈上最短邊 $\times$ 鏈上邊權和 最大
$n,m \leq 2 \times 10^5$
sol:
如果沒有 m 的限制,就是一個點分治
對於分治中心往下的每一條鏈,我們記一個資訊 $(mi,su)$ 表示這條鏈上最小值為 $mi$ ,權值和為 $su$
按 $mi$ 的下標插入樹狀陣列,每次查詢即可
注意每次按兒子順序正著做一遍,反著做一遍·
這樣寫可以 $O(nlog^2n)$ 拿到 80 分
或者我們用 LCT 維護子樹資訊,很好想但。。。不太可寫
正解基於下面一個結論:如果樹上一個連通塊 u 的一條直徑為 $u_i,u_j$ ,一個連通塊 v 的一條直徑為 $v_i,v_j$
則 $(u_i,v_i),(u_i,v_j),(u_j,v_i),(u_j,v_j)$ 其中至少一個為連通塊 u,v 的並集的直徑
於是對於 80 分我們可以按從大到小加入每條邊然後直接統計答案
對於 100 分,除了加入,我們還有刪除操作
這個時候我們可以線段樹按時間分治,用一個可撤銷的並查集來維護連通性
或者 LCT
#include<cstdio> #include<cctype> #include<queue> #include<ctime> #include<cstring> #include<algorithm> #define dwn(i,s,t) for(int i=s;i>=t;i--) #define rep(i,s,t) for(int i=s;i<=t;i++) #define ren for(int i=first[x];i;i=next[i]) using namespace std; inline int read() { int x=0,f=1;char c=getchar(); for(;!isdigit(c);c=getchar()) if(c=='-') f=-1; for(;isdigit(c);c=getchar()) x=x*10+c-'0'; return x*f; } typedef long long ll; typedef pair<int,int> Pair; const int maxn=160010; struct Edge { int u,v,w; bool operator < (const Edge& ths) const {return w>ths.w;} }E[maxn]; int n,lim,first[maxn],next[maxn<<1],dis[maxn<<1],to[maxn<<1],e; void AddEdge(int u,int v,int w) { to[++e]=v;dis[e]=w;next[e]=first[u];first[u]=e; to[++e]=u;dis[e]=w;next[e]=first[v];first[v]=e; } ll mn[20][maxn*2],dep[maxn]; int pos[maxn*2],cnt; void dfs(int x,int fa) { mn[0][++cnt]=dep[x];pos[x]=cnt; ren if(to[i]!=fa) { dep[to[i]]=dep[x]+dis[i]; dfs(to[i],x); mn[0][++cnt]=dep[x]; } } int Log[maxn*2]; void init() { Log[0]=-1;rep(i,1,cnt) Log[i]=Log[i>>1]+1; for(int j=1;(1<<j)<=cnt;j++) for(int i=1;i+(1<<j)-1<=cnt;i++) mn[j][i]=min(mn[j-1][i],mn[j-1][i+(1<<j-1)]); } ll query(int x,int y) { ll ans=dep[x]+dep[y];x=pos[x];y=pos[y];if(x>y) swap(x,y); int k=Log[y-x+1]; return ans-2*min(mn[k][x],mn[k][y-(1<<k)+1]); } Pair A[maxn]; Pair merge(Pair A,Pair B) { int x1=A.first,y1=A.second; int x2=B.first,y2=B.second; int x=x1,y=y1; if(query(x2,y2)>query(x,y)) x=x2,y=y2; if(query(x2,y1)>query(x,y)) x=x2,y=y1; if(query(x2,x1)>query(x,y)) x=x2,y=x1; if(query(y2,x1)>query(x,y)) x=y2,y=x1; if(query(y2,y1)>query(x,y)) x=y2,y=y1; return make_pair(x,y); } int pa[maxn],size[maxn]; int findset(int x) {return x==pa[x]?x:findset(pa[x]);} int end[maxn]; struct Data { int x,y,pax,sizey; ll ansv; Pair datay; }S[maxn]; int ToT; ll maxlen,ans; void link(int x,int y) { x=findset(x);y=findset(y); if(x==y) return; if(size[x]>size[y]) swap(x,y); S[++ToT]=(Data){x,y,pa[x],size[y],maxlen,A[y]}; pa[x]=y;if(size[x]==size[y]) size[y]++;A[y]=merge(A[y],A[x]); maxlen=max(maxlen,query(A[y].first,A[y].second)); } void restore(int begin) { while(ToT!=begin) { int x=S[ToT].x,y=S[ToT].y,pax=S[ToT].pax,sizey=S[ToT].sizey; maxlen=S[ToT].ansv;Pair z=S[ToT--].datay; pa[x]=pax;size[y]=sizey;A[y]=z; } } int ls[maxn<<1],rs[maxn<<1],rt; void buildtree(int& o,int l,int r) { o=++ToT;if(l==r) return; int mid=l+r>>1; buildtree(ls[o],l,mid);buildtree(rs[o],mid+1,r); } int first2[maxn<<1],nxt2[maxn*20],id[maxn*20]; void AddMark(int x,int val) { id[++cnt]=val;nxt2[cnt]=first2[x];first2[x]=cnt; } void query(int o,int l,int r,int ql,int qr,int val) { if(ql<=l&&r<=qr) AddMark(o,val); else { int mid=l+r>>1; if(ql<=mid) query(ls[o],l,mid,ql,qr,val); if(qr>mid) query(rs[o],mid+1,r,ql,qr,val); } } void solve(int o,int l,int r) { int begin=ToT; for(int i=first2[o];i;i=nxt2[i]) link(E[id[i]].u,E[id[i]].v); if(l<r) { int mid=l+r>>1; solve(ls[o],l,mid); solve(rs[o],mid+1,r); } else ans=max(ans,maxlen*E[l].w); restore(begin); } int main() { freopen("route.in","r",stdin); freopen("route.out","w",stdout); n=read();lim=read(); rep(i,1,n-1) { E[i].u=read();E[i].v=read();E[i].w=read(); AddEdge(E[i].u,E[i].v,E[i].w); } buildtree(rt,1,n-1);ToT=0; dfs(1,0);init();cnt=0; sort(E+1,E+n); rep(i,1,n) pa[i]=i,A[i]=make_pair(i,i),size[i]=1; int j=n-1; dwn(i,n-1,1) { while(E[i].w-E[j].w>lim) j--; end[i]=j;query(rt,1,n-1,i,end[i],i); } solve(rt,1,n-1); printf("%lld\n",ans); return 0; }T3