nfls
我决定每天都写随笔。
大抵是觉得自己太菜了吧。
9.11
\(10611\):
A.
为什么赛时不会呢?
每条管道没有流量或者有单向的流量,每个点处流入的流量之和等于流出的流量之和。
这句话的意思是说对于一个点,至多有一条与之连接的边可以不查询。
所以想到对于那些不查询的边,构成了一片森林。
为使得总代价尽可能的小,我们可以让不查询的森林的全职总和尽可能的大,于是变成了一道最大生成森林。
负权边特殊处理一下即可。
#include <bits/stdc++.h>
using namespace std;
const int N=5e5+5;
#define ll long long
int n,m,tot;
struct edge{
int x,y,w;
}e[N];
int fa[N];
int find(int x){
if(x==fa[x]) return x;
return fa[x]=find(fa[x]);
}
bool cmp(edge a,edge b){ return a.w>b.w; }
ll ans;
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++){
int x,y,w;
scanf("%d%d%d",&x,&y,&w);
if(w<0) ans+=w;
else e[++tot]={x,y,w};
}
sort(e+1,e+tot+1,cmp);
for(int i=1;i<=n;i++) fa[i]=i;
for(int i=1;i<=tot;i++){
int x=find(e[i].x),y=find(e[i].y);
if(x==y){
ans+=e[i].w;
continue;
}
fa[x]=y;
}
printf("%lld",ans);
return 0;
}
B.
有傻逼赛时写了个双 \(\log\) 巨大麻烦的做法因为数组开小狂砍 \(40 pts\) ,是谁呢。
先说一下我的思路。
一开始的时候我拓扑排序,直接放,对拍发现被 hack 了。
6
2 5 4 2 4 2
2 1
3 2
4 2
5 1
6 2
这样会输出 \(10\),但实际上答案是 \(9\) 。
考虑按照 \(a_i+dep_i\) 从大到小排序,最大的尽可能的先放,只可能在 \([0,n-1]\) 的时刻出发。
对于一个点,它要在它子树内所有节点放完之后才能放,所以我们在这个节点上挂一段区间 \([l,r]\) 表示这个节点的子树应该在 \([l,r]\) 的顺序里出发。
然后线段树求一下距离最近的被标记过的祖先节点,这就是 这道题 。
先放一下 \(O(n^2)\) 的代码。
#include <bits/stdc++.h>
using namespace std;
const int N=3e5+5;
int n,ans;
int a[N],t[N];
int dep[N],dfn[N],siz[N],f[N],tot;
vector<int> g[N];
int l[N],r[N],vis[N];
void dfs(int x,int fa){
dep[x]=dep[fa]+1;
dfn[x]=++tot;
siz[x]=1,f[x]=fa;
for(auto y:g[x]) if(y!=fa) dfs(y,x),siz[x]+=siz[y];
}
struct node{ int x,id; }b[N];
bool cmp(node x,node y){ return x.x>y.x; }
int query(int x){
while(!vis[x]) x=f[x];
return x;
}
void modify(int x){
int c=siz[x];
while(!vis[x]) siz[x]-=c,x=f[x];
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%d",&a[i]),t[i]=i;
for(int i=1;i<n;i++){
int x,y;
scanf("%d%d",&x,&y);
g[x].push_back(y);
g[y].push_back(x);
}
dep[0]=-1;
dfs(1,0);
for(int i=1;i<=n;i++) b[i]={a[i]+dep[i],i};
sort(b+1,b+n+1,cmp);
vis[0]=1;
for(int i=1;i<=n;i++){
int id=b[i].id,x=query(id);
l[id]=l[x],r[id]=l[id]+siz[id]-1;
ans=max(ans,r[id]+b[i].x);
l[x]+=siz[id];
modify(id);
vis[id]=1;
}
printf("%d",ans);
return 0;
}
优化完之后是 \(O(n \log ^2 n)\) 的。
#include <bits/stdc++.h>
using namespace std;
const int N=3e5+5;
int n,ans;
int a[N];
int dep[N],siz[N],f[N],mxson[N];
int dfn[N],top[N],pos[N],tot;
vector<int> g[N];
void dfs1(int x,int fa){
dep[x]=dep[fa]+1;
siz[x]=1,f[x]=fa;
for(auto y:g[x]){
if(y==fa) continue;
dfs1(y,x);
siz[x]+=siz[y];
if(siz[mxson[x]]<siz[y]) mxson[x]=y;
}
}
void dfs2(int x,int topf){
dfn[x]=++tot;
pos[tot]=x;
top[x]=topf;
if(!mxson[x]) return;
dfs2(mxson[x],topf);
for(auto y:g[x]) if(y!=f[x]&&y!=mxson[x]) dfs2(y,y);
}
struct node{ int x,id; }b[N];
bool cmp(node x,node y){ return x.x>y.x; }
struct tree{
int l,r,siz,d;
}t[N*4];
void build(int p,int l,int r){
t[p].l=l,t[p].r=r;
t[p].siz=t[p].d=0;
if(l==r){
t[p].siz=siz[pos[l]];
return;
}
int mid=(l+r)>>1;
build(p<<1,l,mid);
build(p<<1|1,mid+1,r);
}
int query(int p,int x){
if(t[p].l==t[p].r) return t[p].siz;
int mid=(t[p].l+t[p].r)>>1;
int res=t[p].siz;
if(x<=mid) res+=query(p<<1,x);
else res+=query(p<<1|1,x);
return res;
}
void modify(int p,int l,int r,int v){
if(t[p].l==l&&t[p].r==r){
t[p].siz+=v;
return;
}
int mid=(t[p].l+t[p].r)>>1;
if(r<=mid) modify(p<<1,l,r,v);
else if(l>mid) modify(p<<1|1,l,r,v);
else modify(p<<1,l,mid,v),modify(p<<1|1,mid+1,r,v);
}
void add(int x,int y,int v){
while(top[x]!=top[y]){
if(dep[top[x]]<dep[top[y]]) swap(x,y);
modify(1,dfn[top[x]],dfn[x],v);
x=f[top[x]];
}
if(dep[x]<dep[y]) swap(x,y);
modify(1,dfn[y],dfn[x],v);
}
void update(int p,int l,int r,int x){
if(t[p].l==l&&t[p].r==r){
if(dep[t[p].d]<dep[x]) t[p].d=x;
return;
}
int mid=(t[p].l+t[p].r)>>1;
if(r<=mid) update(p<<1,l,r,x);
else if(l>mid) update(p<<1|1,l,r,x);
else update(p<<1,l,mid,x),update(p<<1|1,mid+1,r,x);
}
int q(int p,int x){
if(t[p].l==t[p].r) return t[p].d;
int ans=t[p].d;
int mid=(t[p].l+t[p].r)>>1;
if(x<=mid){
int t=q(p<<1,x);
if(dep[t]>dep[ans]) ans=t;
}else{
int t=q(p<<1|1,x);
if(dep[t]>dep[ans]) ans=t;
}
return ans;
}
int l[N],r[N];
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
for(int i=1;i<n;i++){
int x,y;
scanf("%d%d",&x,&y);
g[x].push_back(y);
g[y].push_back(x);
}
dep[0]=-1;
dfs1(1,0);
dfs2(1,1);
build(1,1,n);
for(int i=1;i<=n;i++) b[i]={a[i]+dep[i],i};
sort(b+1,b+n+1,cmp);
for(int i=1;i<=n;i++){
int id=b[i].id;
int s=query(1,dfn[id]);
int x=q(1,dfn[id]);
l[id]=l[x],r[id]=l[id]+s-1;
ans=max(ans,r[id]+b[i].x);
l[x]+=s;
add(x,id,-s);
update(1,dfn[id],dfn[id]+siz[id]-1,id);
}
printf("%d",ans);
return 0;
}
很小丑,直接二分可以做到 \(O(n \log n)\) 。

C.
题解很清楚了。

#include <bits/stdc++.h>
using namespace std;
const int N=5e5+5;
#define pii pair<int,int>
#define ll long long
int n,m;
vector<pii> g[N];
int a[N];
struct tree{
int l,r;
ll f,val,tag;
}t[N*4];
void pushup(int p){
t[p].f=max(t[p<<1].f,t[p<<1|1].f);
t[p].val=max(t[p<<1].val,t[p<<1|1].val);
}
void build(int p,int l,int r){
t[p].l=l,t[p].r=r;
t[p].f=t[p].val=t[p].tag=0;
if(l==r){
t[p].val=t[p].f=a[l];
return;
}
int mid=(l+r)>>1;
build(p<<1,l,mid);
build(p<<1|1,mid+1,r);
pushup(p);
}
void pushdown(int p){
if(t[p].tag>t[p<<1].tag){
t[p<<1].tag=t[p].tag;
t[p<<1].f=max(t[p<<1].f,t[p<<1].val+t[p].tag);
}
if(t[p].tag>t[p<<1|1].tag){
t[p<<1|1].tag=t[p].tag;
t[p<<1|1].f=max(t[p<<1|1].f,t[p<<1|1].val+t[p].tag);
}
}
void modify(int p,int l,int r,int v){
if(t[p].l==l&&t[p].r==r){
if(v>t[p].tag){
t[p].tag=v;
t[p].f=max(t[p].f,t[p].val+v);
}
return;
}
pushdown(p);
int mid=(t[p].l+t[p].r)>>1;
if(r<=mid) modify(p<<1,l,r,v);
else if(l>mid) modify(p<<1|1,l,r,v);
else modify(p<<1,l,mid,v),modify(p<<1|1,mid+1,r,v);
pushup(p);
}
int stk[N],tp;
ll ans[N];
ll query(int p,int l,int r){
if(t[p].l==l&&t[p].r==r) return t[p].f;
pushdown(p);
int mid=(t[p].l+t[p].r)>>1;
if(r<=mid) return query(p<<1,l,r);
else if(l>mid) return query(p<<1|1,l,r);
else return max(query(p<<1,l,mid),query(p<<1|1,mid+1,r));
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
scanf("%d",&m);
for(int i=1;i<=m;i++){
int l,r;
scanf("%d%d",&l,&r);
g[l].push_back({r,i});
}
build(1,1,n);
for(int i=n;i>=1;i--){
for(int j=tp;j>=1;j--){
if(j!=tp&&a[stk[j+1]]>=a[i]||2*stk[j]-i>n) break;
modify(1,2*stk[j]-i,n,a[i]+a[stk[j]]);
}
while(tp&&a[i]>=a[stk[tp]]) tp--;
stk[++tp]=i;
for(auto y:g[i]) ans[y.second]=query(1,i,y.first);
}
for(int i=1;i<=m;i++) printf("%lld\n",ans[i]);
return 0;
}
D.
留坑,但大抵不会再补了。
\(20035\):
A.
简单题,怎么做都行,并查集查一下 \(sum_{find(1)}\) 什么时候合法即可。
我又小丑了,这个题完全不用二分,正着做就行。
#include <bits/stdc++.h>
using namespace std;
#define ll long long
const int N=1e6+5;
int n,w;
struct node{ int x,y,w; }e[N];
int l,r;
int fa[N],a[N];
ll sum[N];
int find(int x){
if(fa[x]==x) return x;
return fa[x]=find(fa[x]);
}
void merge(int x,int y){
x=find(x),y=find(y);
if(x==y) return;
fa[x]=y;
sum[y]+=sum[x];
}
bool check(int x){
for(int i=1;i<=n;i++) fa[i]=i,sum[i]=a[i];
for(int i=1;i<n;i++)
if(e[i].w<=x) merge(e[i].x,e[i].y);
return sum[find(1)]>=w;
}
int main(){
scanf("%d%d",&n,&w);
for(int i=2;i<=n;i++) scanf("%d",&a[i]);
for(int i=1;i<n;i++){
scanf("%d%d%d",&e[i].x,&e[i].y,&e[i].w);
r=max(r,e[i].w);
}
while(l<=r){
int mid=(l+r)>>1;
if(check(mid)) r=mid-1;
else l=mid+1;
}
printf("%d",l);
return 0;
}
B.
先按画框大小从大到小排序,画框肯定选择前面的连续个。
再按照美观度给画从大到小排序,当美观度相同时,必然选大小最大的,把小的留给后面的不是很大的画框。
能选就选,不能选就往后看,贪心即可,简单题。
#include <bits/stdc++.h>
using namespace std;
const int N=1e5+5;
int n,m;
struct node{ int s,v; }a[N];
int c[N];
bool cmp1(node a,node b){
if(a.v==b.v) return a.s>b.s;
return a.v>b.v;
}
bool cmp2(int x,int y){ return x>y; }
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++) scanf("%d%d",&a[i].s,&a[i].v);
sort(a+1,a+n+1,cmp1);
for(int i=1;i<=m;i++) scanf("%d",&c[i]);
sort(c+1,c+m+1,cmp2);
int tp=1,ans=0;
for(int i=1;i<=n;i++) if(tp<=m&&a[i].s<=c[tp]) ans++,tp++;
printf("%d",ans);
return 0;
}
C.
一道很好的题。
考虑序列上怎么做,设 \(dp_i\) 表示到第 \(i\) 个位置时的分段数方案。
转移,对于每一个 \(gcd(j...i) > 1\) 的 \(j\) ,都有 \(dp_i+=dp_{j-1}\) 。
所以设 \(s_i\) 为前 \(i\) 项 dp 值的前缀和,那么本质就变成了找到最后一个 \(gcd(j...i) = 1\) 的位置 \(j\) , \(dp_i=s_{i-1}-s_{j-1}\) 。
找的过程可以用二分和 st 表解决。
但这个题是环,断环为链,从 \(1\) 断开,枚举结尾几个元素和 \(1\) 号元素并在一起。
当 \(1\) 号元素并起来的时候大小改变,就重新做一次 dp ,容易发现只有至多 \(\log\) 次改变,所以总复杂度 \(O(n \log^2 n)\) 。
细节是当每次做 dp 的时候都有可能多算了一种方案(仅在 \(gcd(1...n)>1\) 时有效) ,所以符合条件时应减掉这种情况。
#include <bits/stdc++.h>
using namespace std;
#define ll long long
const int mod=1e9+7;
const int N=1e5+5;
int n,gc;
int a[N];
ll ans,dp[N],sum[N];
int g[N][25];
int check(int l,int r){
int k=(int)log2(r-l+1);
return __gcd(g[l][k],g[r-(1<<k)+1][k]);
}
void solve(int n){
for(int i=1;i<=n;i++) g[i][0]=a[i];
for(int j=1;j<=20;j++)
for(int i=1;i+(1<<j)-1<=n;i++) g[i][j]=__gcd(g[i][j-1],g[i+(1<<(j-1))][j-1]);
dp[0]=sum[0]=1;
for(int i=1;i<=n;i++){
int l=1,r=i-1;
while(l<=r){
int mid=(l+r)>>1;
if(check(mid,i)==1) l=mid+1;
else r=mid-1;
}
if(!r) dp[i]=sum[i-1];
else dp[i]=(sum[i-1]-sum[r-1]+mod)%mod;
sum[i]=(sum[i-1]+dp[i])%mod;
}
}
int main(){
scanf("%d",&n);
int f=0;
for(int i=1;i<=n;i++) scanf("%d",&a[i]),f|=(a[i]==1),gc=__gcd(gc,a[i]);
if(f){
printf("0");
return 0;
}else if(n==1){
printf("1");
return 0;
}
solve(n);
ans=dp[n];
for(int i=n;i>=2;i--){
int lst=a[1];
a[1]=__gcd(a[1],a[i]);
if(a[1]==1) break;
if(a[1]!=lst) solve(i-1);
ans=(ans+dp[i-1])%mod;
if(gc>1) ans=(ans-1+mod)%mod;
}
printf("%lld",ans);
return 0;
}
D.
一道很好的题,树哈希。
考虑两棵子树,当它们按照顺序的每个节点的出度均相同,即它们 \(dfs\) 序上的哈希值一样时,相同。
所以当成字符串哈希就行了。
注意到是距离大于 \(k\) 的子树需要删掉,这体现在合并 \(dfs\) 序的时候不合并这一段即可。
另外值得一提的是,如果直接二分,需要寻找 \(k\) 级祖先,这样做是双 \(\log\) 的。
但如果我们换一种方式,直接倍增,检查是否合法,如果合法就直接把祖先更新成这次的祖先,这样做复杂度就降至 \(O(n \log n)\) 。
#include <bits/stdc++.h>
using namespace std;
#define ull unsigned long long
const int N=1e5+5;
int n;
int pos[N],l[N],r[N],tot;
int f[N][25];
vector<int> g[N],v[N];
ull bas[N],h[N];
void dfs(int x){
l[x]=++tot;
pos[tot]=x;
for(int i=1;i<=20;i++) f[x][i]=f[f[x][i-1]][i-1];
h[tot]=h[tot-1]*N+g[x].size();
for(auto y:g[x]) dfs(y);
r[x]=tot;
}
void calc(ull &x,int l,int r){
if(l>r) return;
x=x*bas[r-l+1]+h[r]-h[l-1]*bas[r-l+1];
}
unordered_set<ull> s;
int a[N],b[N];
bool check(int x){
s.clear();
for(int i=1;i<=n;i++) v[i].clear();
for(int i=1;i<=n;i++){
b[pos[i]]=f[a[pos[i]]][x];
if(b[pos[i]]) v[b[pos[i]]].push_back(pos[i]);
}
for(int i=1;i<=n;i++){
if(v[i].empty()) continue;
int lst=l[i];
ull res=0;
for(auto y:v[i]){
calc(res,lst,l[y]-1);
lst=r[y]+1;
}
calc(res,lst,r[i]);
if(s.find(res)!=s.end()) return 1;
s.insert(res);
}
return 0;
}
int ans;
int main(){
scanf("%d",&n);
bas[0]=h[0]=1;
for(int i=1;i<=n;i++){
bas[i]=bas[i-1]*N;
a[i]=i;
int c;
scanf("%d",&c);
while(c--){
int x;
scanf("%d",&x);
g[i].push_back(x);
f[x][0]=i;
}
}
dfs(1);
for(int i=20;i>=0;i--){
if(check(i)){
ans|=(1<<i);
for(int j=1;j<=n;j++) a[j]=b[j];
}
}
printf("%d",ans);
return 0;
}
9.12
\(10611\):
A.
考虑距离不超过 \(2\) 怎么解决,设 \(tag_{x,0}\) 表示这个点自己的异或值,\(tag_{x,1}\) 表示该节点子树中与之距离为 \(1\) 的点的异或和,\(tag_{x,2}\) 表示该节点子树中与之距离为 \(2\) 的点的异或和。
对于每一个点 \(x\),答案为 \(tag_{fa,1} \oplus tag_{fa,0} \oplus tag_{gfa,0} \oplus tag_{x,1} \oplus tag_{x,2}\) 。
对于修改操作,一个点的更改只会影响到 \(tag_{x,0}\) 、\(tag_{fa,1}\) 和 \(tag_{gfa,2}\),分别修改即可。
upd:果然挂了,有两个问题,第一个是 res*i*i 可能会爆 LL,另一个问题是如果这个点是根节点,那么它自己就不会被算到,\(100 -> 10\) 。
#include <bits/stdc++.h>
using namespace std;
#define ll long long
const int N=1e6+5;
const int mod=1e9+7;
int n,q;
vector<int> g[N];
int f[N];
ll tag[N][3];
int a[N];
void dfs(int x,int fa){
f[x]=fa;
tag[x][0]=a[x];
if(f[x]) tag[f[x]][1]^=a[x];
if(f[f[x]]) tag[f[f[x]]][2]^=a[x];
for(auto y:g[x]) if(y!=fa) dfs(y,x);
}
ll ans;
int main(){
scanf("%d%d",&n,&q);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
for(int i=1;i<n;i++){
int x,y;
scanf("%d%d",&x,&y);
g[x].push_back(y);
g[y].push_back(x);
}
dfs(1,0);
for(int i=1;i<=q;i++){
int x;
ll v;
scanf("%d%lld",&x,&v);
if(f[x]) tag[f[x]][1]^=(tag[x][0]^v);
if(f[f[x]]) tag[f[f[x]]][2]^=(tag[x][0]^v);
tag[x][0]=v;
ll res=tag[x][1]^tag[x][2];
res^=tag[f[x]][1]^tag[f[x]][0]^tag[f[f[x]]][0];
if(x==1) res^=tag[x][0];
ans=(ans+res*i%mod*i%mod)%mod;
}
printf("%lld",ans);
return 0;
}
B.
呃呃赛时又不会。
留坑待补。
C.
做差分得到一些位置上是 \(1\) ,\(1\) 的个数不会超过 \(2\cdot k\) 。也就是说每次选两个距离为奇质数的点可以消掉两个 \(1\) 。
由此可见,当两个点相差为奇质数,只需要操作一次;当它们相差为偶数,只需要操作两次;其余情况需要三次。
所以我们贪心地想让奇质数匹配尽可能地多。还有一个性质是,两个点的距离为奇质数时,它们的奇偶性一定不同,所以根据奇偶性建二分图,跑匈牙利或网络流都可以通过。
筛法从 \(2\) 开始捏。
#include <bits/stdc++.h>
using namespace std;
const int N=1e3+5,M=1e7+5;
int k;
int a[M],p[M];
void pre(){
for(int i=2;i<M;i++)
if(!p[i])
for(int j=2;i*j<M;j++) p[i*j]=1;
p[1]=p[2]=1;
}
int mch[N],used[N];
vector<int> s[2],g[N];
int dfs(int x){
for(auto y:g[x]){
if(used[y]) continue;
used[y]=1;
if(!mch[y]||dfs(mch[y])){
mch[y]=x;
return 1;
}
}
return 0;
}
int ans;
int main(){
pre();
scanf("%d",&k);
while(k--){
int x;
scanf("%d",&x);
a[x]=1;
}
for(int i=1;i<M;i++)
if(a[i]^a[i-1]) s[i&1].push_back(i);
for(int i=0;i<s[0].size();i++){
int x=s[0][i];
for(int j=0;j<s[1].size();j++){
int y=s[1][j];
if(!p[abs(x-y)]) g[i+1].push_back(j+1);
}
}
for(int i=0;i<s[0].size();i++){
memset(used,0,sizeof used);
ans+=dfs(i+1);
}
int a=s[0].size()-ans,b=s[1].size()-ans;
ans+=a+b;
a=a&1,b=b&1;
if(a&b) ans++;
else if(a) ans++;
else if(b) ans+=2;
printf("%d",ans);
return 0;
}
贪心和数据结构专题
A.
线段树上二分,如果有左右端点限制会很麻烦,所以我只会整个序列上的线段树二分。
首先对于第一个问题,本质上是求序列中第一个大于等于 \(y\) 的位置 \(pos\),然后 \([pos,x]\) 区间赋值为 \(y\) 。
对于第二个问题,有一个左端点的限制不好做,那我们可以将 \(y\) 加上 \(\sum_{i=1}^{x-1} a_i\) ,这样就等价于前面的东西都可以选一遍,那么只要统计答案的时候减去前面的 \(x-1\) 即可。
但我不是很理解为什么时间复杂度是 \(O(n \log y \log n)\) 的,引用一下 kls 的题解:
每个人进商店买东西构成最多 \(\log y\) 个连续段。
#include <bits/stdc++.h>
using namespace std;
#define ll long long
const int N=2e5+5;
int n,m;
int a[N];
struct tree{
int l,r;
ll mn,tag,sum;
}t[N*4];
void pushup(int p){
t[p].mn=min(t[p<<1].mn,t[p<<1|1].mn);
t[p].sum=t[p<<1].sum+t[p<<1|1].sum;
}
void build(int p,int l,int r){
t[p].l=l,t[p].r=r;
t[p].mn=t[p].sum=t[p].tag=0;
if(l==r){
t[p].mn=t[p].sum=a[l];
return;
}
int mid=(l+r)>>1;
build(p<<1,l,mid);
build(p<<1|1,mid+1,r);
pushup(p);
}
void pushdown(int p){
if(t[p].tag){
t[p<<1].mn=t[p].tag,t[p<<1|1].mn=t[p].tag;
t[p<<1].sum=t[p].tag*(t[p<<1].r-t[p<<1].l+1);
t[p<<1|1].sum=t[p].tag*(t[p<<1|1].r-t[p<<1|1].l+1);
t[p<<1].tag=t[p<<1|1].tag=t[p].tag;
t[p].tag=0;
}
}
int q(int p,int v){
if(t[p].l==t[p].r){
if(t[p].mn<v) return t[p].l;
return t[p].l+1;
}
pushdown(p);
int mid=(t[p].l+t[p].r)>>1;
if(t[p<<1].mn<v) return q(p<<1,v);
else return q(p<<1|1,v);
}
ll qsum(int p,int r){
if(t[p].r==r) return t[p].sum;
pushdown(p);
int mid=(t[p].l+t[p].r)>>1;
if(r<=mid) return qsum(p<<1,r);
else return t[p<<1].sum+qsum(p<<1|1,r);
}
int query(int p,ll &v){
if(v<t[p].mn) return 0;
if(t[p].l==t[p].r){
v-=t[p].sum;
return 1;
}
pushdown(p);
int ans=0;
if(t[p<<1].sum<=v) v-=t[p<<1].sum,ans+=t[p<<1].r-t[p<<1].l+1;
else ans+=query(p<<1,v);
ans+=query(p<<1|1,v);
return ans;
}
void modify(int p,int l,int r,ll v){
if(t[p].l==l&&t[p].r==r){
t[p].tag=t[p].mn=v;
t[p].sum=v*(r-l+1);
return;
}
pushdown(p);
int mid=(t[p].l+t[p].r)>>1;
if(r<=mid) modify(p<<1,l,r,v);
else if(l>mid) modify(p<<1|1,l,r,v);
else modify(p<<1,l,mid,v),modify(p<<1|1,mid+1,r,v);
pushup(p);
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
build(1,1,n);
while(m--){
int op,x;
ll y;
scanf("%d%d%lld",&op,&x,&y);
if(op==1){
int p=q(1,y);
if(p<=x) modify(1,p,x,y);
}else{
if(x!=1) y+=qsum(1,x-1);
printf("%d\n",query(1,y)-x+1);
}
}
return 0;
}
一些杂题
G.
好久之前的题了,复习一下网络流。
考虑建图跑费用流,费用流是因为这个题有得分这一权值限制。
源点连角色,流量为 \(1\),权值为 \(c\),角色连接区间,流量为 \(1\),权值为 \(0\),因为是区间,所以不能直接连边,考虑线段树优化建图。
线段树上父亲连孩子,这里很需要注意流量到底是什么,显然我们对于一个区间,可以流过长度大小的流量,权值为 \(0\),叶子结点连接汇点,流量为 \(1\),权值为 \(0\)。
至此建图结束,跑最大费用最大流即可,注意费用流板子里的 \(vis\) 数组有什么用:
-
spfa 时不重复加入队列。
-
最短路建出来的生成图不一定是拓扑图,有可能会死循环,在 dfs 时判掉,防止死循环。
#include <bits/stdc++.h>
using namespace std;
const int N=5e4+5;
#define inf 0x3f3f3f3f
int n;
int s,t;
struct edge{
int to,nxt,w,c;
}e[N*40];
int h[N],now[N],tot=1;
void add(int x,int y,int w,int c){
e[++tot]={y,h[x],w,c};
h[x]=tot;
e[++tot]={x,h[y],0,-c};
h[y]=tot;
}
int cnt,id[N];
void build(int p,int l,int r){
id[p]=++cnt;
if(l==r){
add(id[p],n+1,1,0);
return;
}
int mid=(l+r)>>1;
build(p<<1,l,mid),build(p<<1|1,mid+1,r);
add(id[p],id[p<<1],mid-l+1,0),add(id[p],id[p<<1|1],r-mid,0); //this!!!
}
void modify(int p,int l,int r,int ql,int qr,int x){
if(l==ql&&r==qr){
add(x,id[p],1,0);
return;
}
int mid=(l+r)>>1;
if(qr<=mid) modify(p<<1,l,mid,ql,qr,x);
else if(ql>mid) modify(p<<1|1,mid+1,r,ql,qr,x);
else modify(p<<1,l,mid,ql,mid,x),modify(p<<1|1,mid+1,r,mid+1,qr,x);
}
int dis[N];
int ans,vis[N];
bool spfa(){
for(int i=0;i<=cnt;i++) now[i]=h[i],dis[i]=inf,vis[i]=0;
queue<int> q;
q.push(s);
dis[s]=0;
int f=0;
while(!q.empty()){
int x=q.front();
if(x==t) f=1;
q.pop();
vis[x]=0;
for(int i=h[x];i;i=e[i].nxt){
int y=e[i].to,c=e[i].c;
if(e[i].w>0&&dis[y]>dis[x]+c){
dis[y]=dis[x]+c;
if(!vis[y]){
q.push(y);
vis[y]=1;
}
}
}
}
return f;
}
int dfs(int x,int sum){
if(x==t) return sum;
int flow=0;
vis[x]=1;
for(int &i=now[x];i;i=e[i].nxt){
int y=e[i].to,c=e[i].c;
if(e[i].w>0&&!vis[y]&&dis[y]==dis[x]+c){
int tmp=dfs(y,min(sum,e[i].w));
if(!tmp) dis[y]=inf;
e[i].w-=tmp,e[i^1].w+=tmp;
sum-=tmp,flow+=tmp;
ans+=c*tmp;
if(!sum) break;
}
}
vis[x]=0;
return flow;
}
void dinic(){ while(spfa()) dfs(s,inf); }
int x[N],y[N],mx;
inline int read(){
int x=0;
char c=getchar();
while(c<'0'||c>'9') c=getchar();
while(c>='0'&&c<='9') x=x*10+c-'0',c=getchar();
return x;
}
int main(){
n=read();
for(int i=1;i<=n;i++){
x[i]=read(),y[i]=read();
int c=read();
add(0,i,1,-c);
y[i]--;
mx=max(mx,y[i]);
}
cnt=n+1;
build(1,1,mx);
for(int i=1;i<=n;i++) modify(1,1,mx,x[i],y[i],i);
s=0,t=n+1;
dinic();
printf("%d",-ans);
return 0;
}
G.
一道线性基和图论综合的题目。
这篇题解写的很清楚了。
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define pil pair<int,ll>
const int N=5e4+5;
int n,m;
vector<pil> g[N];
int vis[N];
ll a[65],dis[N];
void insert(ll x){
for(int i=60;i>=0;i--){
if((x>>i)&1){
if(!a[i]){
a[i]=x;
return;
}
x^=a[i];
}
}
}
void dfs(int x,ll res){
vis[x]=1;
dis[x]=res;
for(auto t:g[x]){
int y=t.first;
ll w=t.second;
if(!vis[y]) dfs(y,res^w);
else insert(dis[x]^dis[y]^w);
}
}
ll query(ll x){
for(int i=60;i>=0;i--) x=max(x,x^a[i]);
return x;
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++){
int x,y;
ll w;
scanf("%d%d%lld",&x,&y,&w);
g[x].push_back({y,w});
g[y].push_back({x,w});
}
dfs(1,0);
printf("%lld",query(dis[n]));
return 0;
}
L
几道 dp,毁了我的省队梦。
所以来做网络流了哈哈哈哈!
首先有一个错误的做法,把人和车连边跑最小费用最大流,这样就有一个问题,一个工人只能修一个车(或者一个工人同时修多辆车,这两个问题无法同时解决),所以我们考虑把一个人拆成 \(n\) 个人。
考虑对于一个工人,假设他能修 \(k\) 辆车,这 \(k\) 辆车的修复时间为 \(a_1,a_2,...,a_k\) ,那么第一个人需要等待 \(a_1\) 的时间,第二个人需要等待 \(a_1+a_2\) 的时间,以此类推,第 \(k\) 个人会等待 \(\sum_{i=1}^{k} a_i\) 的时间。
所以总共等待了 \(\sum_{i=1}^{k} a_i \times (k-i+1)\) 的时间。
所以反过来考虑,一个人能为整个等待过程贡献多少的时间呢,显然是 \(a_i \times (k-i+1)\),但这个 \(k\) 是不确定的,所以我们再次转换思路,可以倒着看,一个人作为倒数第 \(i\) 个被修会为等待过程贡献多少时间呢,显然是 \(a_i \times i\),所以以这个作为费用连边跑最小费用最大流即可。
注意一个细节,就是不能每次连到一个分裂的工人的点就顺便连向汇点,因为这样到汇点的边就重复了很多条,导致答案变得很小。
正确处理方法应该是所有中间边连完之后再连后面的边,保证同一个点到汇点的边只有一条。
#include <bits/stdc++.h>
using namespace std;
#define inf 0x3f3f3f3f
const int N=3e3+5,M=5e5+5;
int n,m,s,t;
int tot=1,h[N],now[N];
struct edge{ int to,nxt,w,c; }e[M];
void add(int x,int y,int w,int c){
e[++tot]={y,h[x],w,c};
h[x]=tot;
e[++tot]={x,h[y],0,-c};
h[y]=tot;
}
int dis[N],vis[N];
bool spfa(){
for(int i=s;i<=t;i++) now[i]=h[i],dis[i]=inf,vis[i]=0;
queue<int> q;
dis[s]=0;
q.push(s);
int f=0;
while(!q.empty()){
int x=q.front();
q.pop();
vis[x]=0;
if(x==t) f=1;
for(int i=h[x];i;i=e[i].nxt){
int y=e[i].to,c=e[i].c;
if(e[i].w>0&&dis[y]>dis[x]+c){
dis[y]=dis[x]+c;
if(!vis[y]){
vis[y]=1;
q.push(y);
}
}
}
}
return f;
}
int ans;
int dfs(int x,int sum){
if(x==t) return sum;
int flow=0;
vis[x]=1;
for(int &i=now[x];i;i=e[i].nxt){
int y=e[i].to,c=e[i].c;
if(e[i].w>0&&!vis[y]&&dis[y]==dis[x]+c){
int tmp=dfs(y,min(sum,e[i].w));
if(!tmp) dis[y]=inf;
e[i].w-=tmp,e[i^1].w+=tmp;
sum-=tmp,flow+=tmp;
ans+=tmp*c;
if(!sum) break;
}
}
vis[x]=0;
return flow;
}
void dinic(){ while(spfa()) dfs(s,inf); }
int main(){
scanf("%d%d",&m,&n);
s=0,t=n*(m+1)+1;
for(int i=1;i<=n;i++){
add(0,i,1,0);
for(int j=1;j<=m;j++){
int x;
scanf("%d",&x);
for(int k=1;k<=n;k++) add(i,n*j+k,1,x*k);
}
}
for(int i=1;i<=n*m;i++) add(i+n,t,1,0);
dinic();
printf("%.2lf",ans*1./n);
return 0;
}
I
AC 自动机,好久没写了。
对于 \(S\) 串建立 AC 自动机,那么每次加入一个串 \(T\) ,可以算出这个串对于哪些 \(S\) 有贡献,累加一下即可。
具体来说,建出失配树,每次在 \(Trie\) 树上遍历该次的 \(T\) ,将这个节点在失配树上到根节点的路径都 \(+1\) 。
但这样就会有一个问题,对于一个 \(T\) ,给每个串贡献了不止一次,所以其实我们要做的等价于将每个节点到根节点这条链合并起来,得到一个点集,将点集中的点 \(+1\) 。
这个东西其实相当于树链求并,我们把所有点按照 dfs 序排完序后(可以顺便去个重),得到数组 \(a_n\) 。
对于 \(1\le i \le k\) ,将 \(a_i\) 到根节点的路径 \(+1\) 。
对于 \(1 \le i < k\) ,将 \(lca(a_i,a_{i+1})\) 到根节点的路径 \(-1\) 。
路径加,难道还要写树剖?
事实上是不用的,用一个思维转化,单点加,查询子树和即可,这样直接用 dfs 序就可以了。
#include <bits/stdc++.h>
using namespace std;
const int N=2e6+5;
int n,q;
int t[N][26],fail[N],tot;
int ed[N];
void insert(char s[],int id){
int n=strlen(s),pos=0;
for(int i=0;i<n;i++){
int x=s[i]-'a';
if(!t[pos][x]) t[pos][x]=++tot;
pos=t[pos][x];
}
ed[id]=pos;
}
vector<int> g[N];
void build(){
queue<int> q;
for(int i=0;i<26;i++) if(t[0][i]) q.push(t[0][i]);
while(!q.empty()){
int p=q.front();
q.pop();
g[fail[p]].push_back(p);
for(int x=0;x<26;x++){
if(t[p][x]) fail[t[p][x]]=t[fail[p]][x],q.push(t[p][x]);
else t[p][x]=t[fail[p]][x];
}
}
}
int dfn[N],siz[N],dep[N],cnt,f[N][26];
void dfs(int x){
dfn[x]=++cnt;
siz[x]=1;
f[x][0]=fail[x];
dep[x]=dep[f[x][0]]+1;
for(int i=1;i<=25;i++) f[x][i]=f[f[x][i-1]][i-1];
for(auto y:g[x]) dfs(y),siz[x]+=siz[y];
}
int a[N],tp;
bool cmp(int x,int y){ return dfn[x]<dfn[y]; }
int lca(int x,int y){
if(dep[x]<dep[y]) swap(x,y);
for(int i=25;i>=0;i--)
if(dep[f[x][i]]>=dep[y]) x=f[x][i];
if(x==y) return x;
for(int i=25;i>=0;i--)
if(f[x][i]!=f[y][i]) x=f[x][i],y=f[y][i];
return f[x][0];
}
int c[N];
int lowbit(int x){ return x&(-x); }
void add(int x,int v){ for(int i=x;i<=cnt;i+=lowbit(i)) c[i]+=v; }
void modify(char s[]){
int n=strlen(s),pos=0;
for(int i=0;i<n;i++){
int x=s[i]-'a';
pos=t[pos][x];
a[++tp]=pos;
}
sort(a+1,a+tp+1);
tp=unique(a+1,a+tp+1)-a-1;
sort(a+1,a+tp+1,cmp);
for(int i=1;i<=tp;i++) add(dfn[a[i]],1);
for(int i=1;i<tp;i++) add(dfn[lca(a[i],a[i+1])],-1);
}
int query(int x){
int res=0;
for(int i=x;i;i-=lowbit(i)) res+=c[i];
return res;
}
char s[N];
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%s",s);
insert(s,i);
}
build();
dfs(0);
scanf("%d",&q);
while(q--){
int op;
scanf("%d",&op);
if(op==1){
tp=0;
scanf("%s",s);
modify(s);
}else{
int x;
scanf("%d",&x);
int l=dfn[ed[x]],r=dfn[ed[x]]+siz[ed[x]]-1;
printf("%d\n",query(r)-query(l-1));
}
}
return 0;
}
F
考察了矩阵优化 dp 的一些定义,以及 kmp 算法。
首先考虑一个复杂度为 \(O(n \cdot m^2)\) 的 dp 做法。
\(dp_{i,j}\) 表示大串匹配到第 \(i\) 位,小串匹配到第 \(j\) 位的方案数。
为了使得小串不出现,那么最后的答案就是 \(\sum_{i=0}^{m-1} dp_{n,i}\) 。
考虑转移,假设第 \(i\) 位和第 \(j\) 位相等,那么就会转移到 \(dp_{i+1,j+1}\) 。
那如果不相等呢,在小串上跳,直到相等,得到一个串的长度为 \(k\) ,那么就转移到 \(dp_{i+1,k}\) ,怎么找 \(k\) 呢,我们发现这个跳的过程不就是 kmp 吗。
通过 kmp,预处理出一个 \(f\) 数组,\(f_{j,k}\) 表示在 \(j\) 状态上,增加一个字符到达 \(k\) 状态的方案数。
那么每次 dp 时转移就直接 \(dp_{i,j}=\sum_{k=0}^{m-1} dp_{i-1,k} \times f_{k,j}\) 。
至此,朴素 dp 的做法结束了。
我们用肉眼观察一下这个式子,发现可以套矩阵,于是直接优化就做完了。
\(40pts:\)
#include <bits/stdc++.h>
using namespace std;
const int N=25,M=2e5+5;
int n,m,mod;
char a[N];
int dp[M][N];
int f[N][N],nxt[N];
void add(int &x,int y){ x=(x+y)%mod; }
void kmp(){
for(int i=2,j=0;i<=m;i++){
while(j&&a[i]!=a[j+1]) j=nxt[j];
if(a[i]==a[j+1]) j++;
nxt[i]=j;
}
for(int i=0;i<m;i++){
for(int k=0;k<=9;k++){
int j=i;
while(j&&k+'0'!=a[j+1]) j=nxt[j];
if(k+'0'==a[j+1]) j++;
if(j<m) add(f[i][j],1);
}
}
}
int ans;
int main(){
scanf("%d%d%d%s",&n,&m,&mod,a+1);
kmp();
dp[0][0]=1;
for(int i=1;i<=n;i++){
for(int j=0;j<m;j++){
for(int k=0;k<m;k++){
add(dp[i][j],dp[i-1][k]*f[k][j]%mod);
}
}
}
for(int i=0;i<m;i++) add(ans,dp[n][i]);
printf("%d",ans);
return 0;
}
注意到矩阵不具有交换律,初始矩阵一定要在最前面乘,所以最后的答案应该是第一行,而不是第一列。
#include <bits/stdc++.h>
using namespace std;
const int N=25,M=2e5+5;
int n,m,mod;
char a[N];
int nxt[N];
struct ma{
int x[N][N];
ma(){ memset(x,0,sizeof x); }
}A;
void add(int &x,int y){ x=(x+y)%mod; }
void kmp(){
for(int i=2,j=0;i<=m;i++){
while(j&&a[i]!=a[j+1]) j=nxt[j];
if(a[i]==a[j+1]) j++;
nxt[i]=j;
}
for(int i=0;i<m;i++){
for(int k=0;k<=9;k++){
int j=i;
while(j&&k+'0'!=a[j+1]) j=nxt[j];
if(k+'0'==a[j+1]) j++;
if(j<m) add(A.x[i][j],1);
}
}
}
void mul(ma &m1,ma m2){
ma m3;
for(int i=0;i<m;i++)
for(int j=0;j<m;j++)
for(int k=0;k<m;k++)
add(m3.x[i][j],m1.x[i][k]*m2.x[k][j]%mod);
m1=m3;
}
ma qpow(ma a,int b){
ma res;
for(int i=0;i<m;i++) res.x[i][i]=1;
while(b){
if(b&1) mul(res,a);
mul(a,a);
b>>=1;
}
return res;
}
int ans;
int main(){
scanf("%d%d%d%s",&n,&m,&mod,a+1);
kmp();
ma B=qpow(A,n);
for(int i=0;i<m;i++) add(ans,B.x[0][i]);
printf("%d",ans);
return 0;
}
H
考察单调栈和单调队列的一道综合题目。
首先把删除变成加入,倒着操作使答案只增不减。
对于每一个点,我们可以预处理出它最上面和最下面能到的位置,对于修改,只有这一列有变化,暴力修改。
对于计算答案,只有这一行的答案会发生变化,用双指针套单调队列维护一下即可。
注意不要把 while 写成 if 。
#include <bits/stdc++.h>
using namespace std;
#define inf 0x3f3f3f3f
const int N=2e3+5;
struct node{ int x,y; }a[N];
int n,m,q,t;
char s[N][N];
int f[N][N],g[N][N];
void solve(int y){
int p=0;
for(int i=1;i<=n;i++){
if(s[i][y]=='X') p=i;
f[i][y]=i-p;
}
p=n+1;
for(int i=n;i>=1;i--){
if(s[i][y]=='X') p=i;
g[i][y]=p-i;
}
}
deque<int> q1,q2;
bool check(int x,int l,int r){
int m1=f[x][r],m2=g[x][r];
if(!q1.empty()) m1=min(m1,f[x][q1.front()]);
if(!q2.empty()) m2=min(m2,g[x][q2.front()]);
return m1+m2>=r-l+2;
}
void add(int x,int y){
while(!q1.empty()&&f[x][y]<=f[x][q1.back()]) q1.pop_back();
q1.push_back(y);
while(!q2.empty()&&g[x][y]<=g[x][q2.back()]) q2.pop_back();
q2.push_back(y);
}
int getans(int x){
q1.clear(),q2.clear();
int res=0;
for(int l=1,r=0;l<=m;l++){
if(r<l-1) r=l-1;
while(r<m&&check(x,l,r+1)) r++,add(x,r);
res=max(res,r-l+1);
if(!q1.empty()&&q1.front()==l) q1.pop_front();
if(!q2.empty()&&q2.front()==l) q2.pop_front();
}
return res;
}
int ans[N];
int main(){
scanf("%d%d%d",&n,&m,&q);
for(int i=1;i<=n;i++) scanf("%s",s[i]+1);
for(int i=1;i<=q;i++){
scanf("%d%d",&a[i].x,&a[i].y);
s[a[i].x][a[i].y]='X';
}
for(int i=1;i<=m;i++) solve(i);
for(int i=1;i<=n;i++) t=max(t,getans(i));
for(int i=q;i>=1;i--){
ans[i]=t;
s[a[i].x][a[i].y]='.';
solve(a[i].y);
t=max(t,getans(a[i].x));
}
for(int i=1;i<=q;i++) printf("%d\n",ans[i]);
return 0;
}

浙公网安备 33010602011771号