[冬令营模拟]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'; return x * 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(); }
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; }