【杂题汇总】NOIP 2022 杂题选做
自己没事卷的题,也有许多其他dalao博客里的杂题
Examination
四个优先队列,人类智慧贪心,详情见这里
AC code
点击查看代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<cmath>
#include<queue>
#include<cstdlib>
#include<algorithm>
#define pii pair<int,int>
using namespace std;
const int maxn=3e5+5;
inline int read()
{
int w=0,f=1;
char ch=getchar();
while(ch<'0' || ch>'9')
{
if(ch=='-') f=-1;
ch=getchar();
}
while(ch>='0' && ch<='9')
{
w=(w<<3)+(w<<1)+(ch^48);
ch=getchar();
}
return w*f;
}
int n,x,y,ans;
priority_queue<pii,vector<pii>,less<pii> > q;
priority_queue<int,vector<int>,less<int> > A,B;
priority_queue<int,vector<int>,greater<int> > not_B;
int main()
{
n=read();
for(int i=1;i<=n;i++)
{
x=read(),y=read();
if(x<y) A.push(x),B.push(y);
else q.push(make_pair(x,y)),ans++;
}
while(!B.empty())
{
int k=B.top();B.pop();
if(!A.empty() && A.top()>=k)
{
A.pop();continue;
}
while(!q.empty() && q.top().first>=k)
{
not_B.push(q.top().second);q.pop();
}
if(not_B.empty()) cout<<-1,exit(0);
ans--,B.push(not_B.top()),not_B.pop();
}
cout<<ans;
return 0;
}
Traffic Jams in the Land
人类智慧线段树,随机跳题跳到这等好题 \(2\leq a_i\leq 6\) ,\(LCM(2,3,4,5,6)\)为60,因此当前时间超过60以后让其对60取模不会影响当前时间是否能被 \(a_i\) 整除,这样每次更新时我们可以只对当前时间为0~59的情况进行处理即可
AC code
点击查看代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<cmath>
#include<algorithm>
using namespace std;
const int maxn=1e5+5;
inline int read()
{
int w=0,f=1;
char ch=getchar();
while(ch<'0' || ch>'9')
{
if(ch=='-') f=-1;
ch=getchar();
}
while(ch>='0' && ch<='9')
{
w=(w<<3)+(w<<1)+(ch^48);
ch=getchar();
}
return w*f;
}
int n,m;
int a[maxn];
struct s_t
{
int l,r;
int val[65];
}t[maxn*4];
void push_up(int p)
{
for(int i=0;i<60;i++)
{
t[p].val[i]=t[p*2+1].val[(i+t[p*2].val[i])%60]+t[p*2].val[i];
}
}
void build(int p,int l,int r)
{
t[p].l=l,t[p].r=r;
if(t[p].l==t[p].r)
{
for(int i=0;i<60;i++)
{
t[p].val[i]=1+(i%a[l]==0);
}
return ;
}
int mid=(l+r)>>1;
build(p*2,l,mid),build(p*2+1,mid+1,r);
push_up(p);
}
void update(int p,int pos)
{
if(t[p].l==t[p].r)
{
for(int i=0;i<60;i++)
{
t[p].val[i]=1+(i%a[pos]==0);
}
return ;
}
int mid=(t[p].l+t[p].r)>>1;
if(pos<=mid) update(p*2,pos);
else update(p*2+1,pos);
push_up(p);
}
int query(int p,int l,int r,int now)
{
if(l<=t[p].l && t[p].r<=r)
{
return t[p].val[now];
}
int mid=(t[p].l+t[p].r)>>1;
int ans=0;
if(l<=mid) ans+=query(p*2,l,r,now);
if(r>mid) ans+=query(p*2+1,l,r,(now+ans)%60);
return ans;
}
int main()
{
n=read(); for(int i=1;i<=n;i++) a[i]=read();
build(1,1,n),m=read();
while(m--)
{
char opt[10];
scanf("%s",opt);
if(opt[0]=='C')
{
int x=read(),d=read();
a[x]=d;update(1,x);
}
else
{
int l=read(),r=read();
printf("%d\n",query(1,l,r-1,0));
}
}
return 0;
}
Poor Turkeys
比较水的黑题 正着比较难枚举,考虑时光倒流,分类讨论如何让一只火鸡活下来,接下来倒着枚举火鸡,如果这只火鸡无法满足活下来的条件,那它就得死,在考虑两只火鸡如何同时活下来,如果不满足则那两只火鸡必死一只,最后统计出ans
AC code
点击查看代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<cmath>
#include<algorithm>
using namespace std;
const int maxn=1e5+10;
inline int read()
{
int w=0,f=1;
char ch=getchar();
while(ch<'0' || ch>'9')
{
if(ch=='-') f=-1;
ch=getchar();
}
while(ch>='0' && ch<='9')
{
w=(w<<3)+(w<<1)+(ch^48);
ch=getchar();
}
return w*f;
}
int n,m,ans;
int x[maxn];
int y[maxn];
bool live[410];
bool s[410][410];
int main()
{
n=read(),m=read();
for(int i=1;i<=m;i++)
{
x[i]=read();
y[i]=read();
}
for(int i=1;i<=n;i++)
{
s[i][i]=true;
for(int j=m;j>=1;j--)
{
bool a=s[i][x[j]];
bool b=s[i][y[j]];
if(a && b) {live[i]=true;break;}
else if(a) s[i][y[j]]=true;
else if(b) s[i][x[j]]=true;
}
}
for(int i=1;i<n;i++)
{
if(live[i]) continue;
for(int j=i+1;j<=n;j++)
{
if(live[j]) continue;
bool cnt=true;
for(int k=1;k<=n;k++)
{
if(s[i][k] && s[j][k]) cnt=false;
}
ans+=cnt;
}
}
cout<<ans;
return 0;
}
Valera and Queries
说实话我真没看出来这是数据结构题 正难则反,对于每个询问,我们可以用总线段数量减去不合法的线段数量,下面说一下判断合法/不合法
我们可以将每一个点转化为区间,区间为上一个点+1到这一个点-1,将所有这样的区间搞出来,判断有没有线段被完全包含于某一一个区间,如果被完全包含的话说明不合法,用离线树状数组统计出来
AC code
点击查看代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<cmath>
#include<algorithm>
using namespace std;
const int maxn=1e6+5;
inline int read()
{
int w=0,f=1;
char ch=getchar();
while(ch<'0' || ch>'9')
{
if(ch=='-') f=-1;
ch=getchar();
}
while(ch>='0' && ch<='9')
{
w=(w<<3)+(w<<1)+(ch^48);
ch=getchar();
}
return w*f;
}
int n,m,cnt;
int t[maxn];
int num[maxn];
int ans[maxn];
struct que
{
int l,r,id;
}q[maxn];
int lowbit(int x){ return x&-x; }
void update(int x)
{
for(;x<maxn;x+=lowbit(x)) t[x]++;
}
int query(int x)
{
int res=0;
for(;x;x-=lowbit(x)) res+=t[x];
return res;
}
bool cmp(que x,que y)
{
if(x.l!=y.l) return x.l>y.l;
if(x.r!=y.r) return x.r<y.r;
return x.id<y.id;
}
int main()
{
n=read(),m=read();
for(int i=1;i<=n;i++)
{
q[++cnt].l=read();
q[cnt].r=read();
}
for(int i=1;i<=m;i++)
{
int tot=read();
for(int j=1;j<=tot;j++)
{
num[j]=read();
}
for(int j=1;j<=tot;j++)
{
if(num[j]>1 && num[j-1]<num[j]-1)
{
q[++cnt].l=num[j-1]+1;
q[cnt].r=num[j]-1;
q[cnt].id=i;
}
}
q[++cnt].l=num[tot]+1;
q[cnt].r=maxn-1,q[cnt].id=i;
}
sort(q+1,q+cnt+1,cmp);
for(int i=1;i<=cnt;i++)
{
if(q[i].id) ans[q[i].id]+=query(q[i].r);
else update(q[i].r);
}
for(int i=1;i<=m;i++)
{
cout<<n-ans[i]<<endl;
}
return 0;
}
Anton and Tree
来自@Clover_BY dalao的讲解(但他没有博客园)
现将每个颜色相同的连通块缩成一个点,然后手模样例可以发现每次都点一个点,使得最大连通块向叶节点的范围扩展了一圈,因此答案就是最大深度-1
使最大深度最小,则我们每次都应该点的点为直径中点,答案即为直径长度的一半
AC code
点击查看代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<cmath>
#include<algorithm>
using namespace std;
const int maxn=200010;
inline int read()
{
int w=0,f=1;
char ch=getchar();
while(ch<'0' || ch>'9')
{
if(ch=='-') f=-1;
ch=getchar();
}
while(ch>='0' && ch<='9')
{
w=(w<<3)+(w<<1)+(ch^48);
ch=getchar();
}
return w*f;
}
int cnt,n,tot,root;
int u[maxn],sign[maxn];
int bel[maxn],deep[maxn];
int val[maxn],head[maxn];
int max_deep[maxn],v[maxn];
struct edge
{
int to,next;
}e[maxn*2];
void add(int x,int y)
{
e[++tot].to=y;
e[tot].next=head[x];
head[x]=tot;
}
void resign(int x,int id)
{
bel[x]=id;
for(int i=head[x];i;i=e[i].next)
{
int to=e[i].to;
if(bel[to] || val[x]!=val[to]) continue;
resign(to,id);
}
}
void dfs_first(int x,int fa)
{
deep[x]=deep[fa]+1;
max_deep[x]=deep[x],sign[x]=x;
for(int i=head[x];i;i=e[i].next)
{
int to=e[i].to;
if(to==fa) continue;
dfs_first(to,x);
if(max_deep[to]>max_deep[x])
{
max_deep[x]=max_deep[to];
sign[x]=sign[to];
}
}
}
void dfs_second(int x,int fa)
{
deep[x]=deep[fa]+1;
max_deep[x]=deep[x];
for(int i=head[x];i;i=e[i].next)
{
int to=e[i].to;
if(to==fa) continue;
dfs_second(to,x);
if(max_deep[to]>max_deep[x])
{
max_deep[x]=max_deep[to];
}
}
}
int main()
{
n=read();
for(int i=1;i<=n;i++)
{
val[i]=read();
}
for(int i=1;i<n;i++)
{
u[i]=read(),v[i]=read();
add(u[i],v[i]),add(v[i],u[i]);
}
for(int i=1;i<=n;i++)
{
if(!bel[i]) resign(i,++cnt);
}
memset(head,0,sizeof(head)),tot=0;
for(int i=1;i<n;i++)
{
if(bel[u[i]]!=bel[v[i]])
{
add(bel[u[i]],bel[v[i]]);
add(bel[v[i]],bel[u[i]]);
}
}
dfs_first(1,0);root=sign[1];
deep[root]=1;dfs_second(root,0);
cout<<(max_deep[root]>>1);
return 0;
}
Tufurama
我们将其转化一下(先调一下)变成了 \(j<i\) 且 \(j\leq a_i\) 且 \(i\leq a_j\)
变一下型,可以确定 \(j\) 的范围 \([1,min(i-1,a_i)]\),则可以在这个范围内找到 \(a_j>i\) 的数量
用主席树即可(不知道为啥这么慢,比rk1树状数组慢了一倍)
AC code
点击查看代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<cmath>
#include<algorithm>
using namespace std;
const int maxn=2e5+5;
inline int read()
{
int w=0,f=1;
char ch=getchar();
while(ch<'0' || ch>'9')
{
if(ch=='-') f=-1;
ch=getchar();
}
while(ch>='0' && ch<='9')
{
w=(w<<3)+(w<<1)+(ch^48);
ch=getchar();
}
return w*f;
}
long long ans;
int n,a[maxn];
int root[maxn],cnt;
struct s_t
{
int ls,rs;
int sum;
}t[maxn<<5];
void update(int &p,int las,int l,int r,int pos)
{
p=++cnt;t[p]=t[las];
if(l==r) {t[p].sum++;return ;}
int mid=(l+r)>>1;
if(pos<=mid) update(t[p].ls,t[las].ls,l,mid,pos);
else update(t[p].rs,t[las].rs,mid+1,r,pos);
t[p].sum=t[t[p].ls].sum+t[t[p].rs].sum;
}
int query(int p,int las,int l,int r,int L,int R)
{
if(L<=l && r<=R) return t[p].sum;
int mid=(l+r)>>1,ans=0;
if(L<=mid) ans+=query(t[p].ls,t[las].ls,l,mid,L,R);
if(R>mid) ans+=query(t[p].rs,t[las].rs,mid+1,r,L,R);
return ans;
}
int main()
{
n=read();
for(int i=1;i<=n;i++)
{
a[i]=min(read(),n);
}
for(int i=1;i<=n;i++)
{
update(root[i],root[i-1],1,n,a[i]);
}
for(int i=1;i<=n;i++)
{
ans+=query(root[min(i-1,a[i])],root[i],1,n,i,n);
}
printf("%lld",ans);
return 0;
}
Sonya and Informatics
设状态 \(dp_{i,j}\) 为第i次操作前面 \(m\) 个数有 \(j\) 个零(\(m\) 为零的个数)分类讨论即可
操作次数太多,矩阵加速递推
AC code
点击查看代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<cmath>
#include<algorithm>
#define int long long
using namespace std;
const int maxn=110;
const int mod=1e9+7;
inline int read()
{
int w=0,f=1;
char ch=getchar();
while(ch<'0' || ch>'9')
{
if(ch=='-') f=-1;
ch=getchar();
}
while(ch>='0' && ch<='9')
{
w=(w<<3)+(w<<1)+(ch^48);
ch=getchar();
}
return w*f;
}
int n,k,m,a[maxn],t;
int A[maxn],B[maxn],C[maxn];
struct Matrix
{
int MA[maxn][maxn];
void init()
{
memset(MA,0,sizeof(MA));
}
void build()
{
for(int i=0;i<=n;i++) MA[i][i]=1;
}
}ans,M;
Matrix operator*(const Matrix &x,const Matrix &y)
{
Matrix z;z.init();
for(int i=0;i<=m;i++)
for(int k=0;k<=m;k++)
for(int j=0;j<=m;j++)
z.MA[i][j]=(z.MA[i][j]+(x.MA[i][k]*y.MA[k][j]%mod))%mod;
return z;
}
void ksm(Matrix a,int b)
{
ans.init(),ans.build();
for(;b;b>>=1,a=a*a)
{
if(b&1) ans=ans*a;
}
}
int KSM(int a,int b)
{
int ans=1;
for(;b;b>>=1,a=(a*a)%mod)
{
if(b&1) ans=(ans*a)%mod;
}
return ans;
}
signed main()
{
n=read(),k=read();
for(int i=1;i<=n;i++) a[i]=read();
for(int i=1;i<=n;i++) m+=(a[i]==0);
for(int i=1;i<=m;i++) t+=(a[i]==0);
for(int i=0;i<=m;i++)
{
A[i]=((m-i)*(m-i))%mod;
B[i]=(i*(n-2*m+i))%mod;
C[i]=(n*(n-1)/2-A[i]-B[i])%mod;
}
M.MA[0][0]=C[0];
M.MA[0][1]=B[1];
for(int i=1;i<m;i++)
{
M.MA[i][i]=C[i];
M.MA[i][i-1]=A[i-1];
M.MA[i][i+1]=B[i+1];
}
M.MA[m][m-1]=A[m-1];
M.MA[m][m]=C[m];
ksm(M,k);
int cnt=0;
for(int i=0;i<=m;i++)
{
cnt=(cnt+ans.MA[i][t])%mod;
}
cout<<ans.MA[m][t]*KSM(cnt,mod-2)%mod;
return 0;
}
[APIO 2015] 八邻旁之桥
分类讨论,显然,同岸的情况可以不用特殊计算
着重讨论隔岸的情况,注意到 \(k\leq 2\)
\(k=1\) 的情况,设桥的位置为pos,可以知道答案为 \(\sum_{i=1}^{n}|x_i-pos|+|y_i-pos|\),那么 \(x_i\) 和 \(y_i\) 可以看做一样的
我们就可以把他们放进一个数组里,显然pos为他们的中位数,可以得出答案
\(k=2\) 的情况,枚举分割点,两侧就变成了 \(k=1\) 的情况,通过权值线段树快速的找出中位数然后求解
AC code
点击查看代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<cmath>
#include<algorithm>
#include<climits>
#define int long long
using namespace std;
const int maxn=200010;
inline int read()
{
int w=0,f=1;
char ch=getchar();
while(ch<'0' || ch>'9')
{
if(ch=='-') f=-1;
ch=getchar();
}
while(ch>='0' && ch<='9')
{
w=(w<<3)+(w<<1)+(ch^48);
ch=getchar();
}
return w*f;
}
int n,k;
namespace first
{
int num[maxn],cnt=0;
int ans,m=0,x,y;
void main()
{
for(int i=1;i<=n;i++)
{
char a[5],b[5];
scanf("%s",a),x=read();
scanf("%s",b),y=read();
if(a[0]==b[0])
{ans+=abs(x-y);continue;}
m++,num[++cnt]=x,num[++cnt]=y;
}
sort(num+1,num+cnt+1);
int mid=(num[cnt>>1]);
for(int i=1;i<=cnt;i++)
ans+=abs(num[i]-mid);
printf("%lld",ans+m);
}
}
namespace second
{
int ans,cnt,tot;
int sum[maxn];
struct s_t
{
int l,r;
int val;
int cnt;
}t[maxn*4];
struct seg
{
int s,t;
}s[maxn];
int num[maxn];
bool cmp(seg a,seg b)
{
return a.s+a.t<b.s+b.t;
}
void build(int p,int l,int r)
{
t[p].l=l,t[p].r=r;
t[p].val=t[p].cnt=0;
if(l==r) return ;
int mid=(l+r)>>1;
build(p*2,l,mid);
build(p*2+1,mid+1,r);
}
void update(int p,int k,int val)
{
if(t[p].l==t[p].r)
{
t[p].cnt++;
t[p].val+=val;
return ;
}
int mid=(t[p].l+t[p].r)>>1;
if(k<=mid) update(p*2,k,val);
else update(p*2+1,k,val);
t[p].cnt=t[p*2].cnt+t[p*2+1].cnt;
t[p].val=t[p*2].val+t[p*2+1].val;
}
int query(int p,int k)
{
if(t[p].l==t[p].r) return num[t[p].l]*k;
int mid=(t[p].l+t[p].r)>>1;
if(k<=t[p*2].cnt) return query(p*2,k);
else return t[p*2].val+query(p*2+1,k-t[p*2].cnt);
}
void work()
{
build(1,1,tot);
for(int i=1;i<=cnt;i++)
{
s[i].s=lower_bound(num+1,num+tot+1,s[i].s)-num;
s[i].t=lower_bound(num+1,num+tot+1,s[i].t)-num;
update(1,s[i].s,num[s[i].s]);
update(1,s[i].t,num[s[i].t]);
sum[i]=abs(t[1].val-2*query(1,i));
}
int mid=LLONG_MAX;
build(1,1,tot);
for(int i=cnt;i>=1;i--)
{
update(1,s[i].s,num[s[i].s]);
update(1,s[i].t,num[s[i].t]);
mid=min(mid,sum[i-1]+abs(t[1].val-2*query(1,cnt-i+1)));
}
printf("%lld",ans+mid);
}
void main()
{
for(int i=1;i<=n;i++)
{
char a[5],b[5];int x,y;
scanf("%s",a),x=read();
scanf("%s",b),y=read();
if(a[0]==b[0])
{ans+=abs(x-y);continue;}
if(x>y) swap(x,y);ans++;
s[++cnt].s=x,s[cnt].t=y;
num[++tot]=x,num[++tot]=y;
}
if(cnt==0) {printf("%lld",ans);return;}
sort(num+1,num+tot+1);
sort(s+1,s+cnt+1,cmp);
tot=unique(num+1,num+tot+1)-num-1;
work();
}
}
signed main()
{
k=read(),n=read();
if(k==1) first::main();
else second::main();
return 0;
}
[APIO 2015] 雅加达的摩天楼
根号分治思想+最短路,对于 \(p\leq \sqrt\frac{n}{3}\) 的情况直接建边,否则对每个doge建一层分层图来搞
还卡了Dij放SPFA过
AC code
点击查看代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<cmath>
#include<vector>
#include<queue>
#include<algorithm>
#define pii pair<int,int>
using namespace std;
const int maxx=105;
const int maxn=30010;
const int maxm=18000010;
const int max_num=3100005;
inline int read()
{
int w=0,f=1;
char ch=getchar();
while(ch<'0' || ch>'9')
{
if(ch=='-') f=-1;
ch=getchar();
}
while(ch>='0' && ch<='9')
{
w=(w<<3)+(w<<1)+(ch^48);
ch=getchar();
}
return w*f;
}
int n,m,tot,siz;
int head[max_num];
bool vis[max_num];
struct edge
{
int to,next,val;
}e[maxm];
int dis[max_num],s,t;
int flor[maxx][maxn];
void add(int x,int y,int val)
{
e[++tot].to=y;
e[tot].next=head[x];
e[tot].val=val;
head[x]=tot;
}
int SPFA(int x,int y)
{
queue <int> q;
memset(dis,0x3f3f3f,sizeof(dis));
dis[x]=0,vis[x]=true,q.push(x);
while(!q.empty())
{
int k=q.front();q.pop();
vis[k]=false;
for(int i=head[k];i;i=e[i].next)
{
int to=e[i].to;
if(dis[to]>dis[k]+e[i].val)
{
dis[to]=dis[k]+e[i].val;
if(vis[to]==false)
vis[to]=true,q.push(to);
}
}
}
return dis[y]==1061109567 ? -1 : dis[y];
}
int main()
{
n=read(),m=read();
siz=sqrt(n/3);
for(int i=1;i<=siz;i++)
for(int j=0;j<=n-1;j++) flor[i][j]=i*n+j;
for(int i=1;i<=siz;i++)
{
for(int j=0;j<=n-1;j++)
{
add(flor[i][j],j,0);
if(j+i>=n) break;
add(flor[i][j],flor[i][i+j],1);
add(flor[i][i+j],flor[i][j],1);
add(flor[i][i+j],i+j,0);
}
}
for(int i=0;i<m;i++)
{
int b=read(),p=read();
if(p<=siz)
add(b,flor[p][b],0);
else
{
for(int j=1;b+p*j<n;j++) add(b,b+p*j,j);
for(int j=1;b-p*j>=0;j++) add(b,b-p*j,j);
}
if(i==0) s=b;
if(i==1) t=b;
}
printf("%d",SPFA(s,t));
return 0;
}
[APIO 2015] 巴厘岛的雕塑
数位DP,还是踏马的分类讨论,\(A=1\) 设状态 \(f_i\) 表示当前位放0最少分几段,从高位到低位枚举,最后与 \(B\) 进行比较
\(A\neq 1\),bool状态 \(dp_{i,j}\) 表示前 \(i\) 个数分成 \(j\) 段这一位能不能为0,然后与上述情况进行类似的转移
AC code
点击查看代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<cmath>
#include<algorithm>
#define int long long
using namespace std;
const int maxn=2010;
inline int read()
{
int w=0,f=1;
char ch=getchar();
while(ch<'0' || ch>'9')
{
if(ch=='-') f=-1;
ch=getchar();
}
while(ch>='0' && ch<='9')
{
w=(w<<3)+(w<<1)+(ch^48);
ch=getchar();
}
return w*f;
}
int ans,res,f[maxn];
bool dp[maxn][maxn];
int s[maxn],n,a,b;
namespace solve
{
void main()
{
for(int w=45;w>=0;w--)
{
memset(dp,false,sizeof(dp));
dp[0][0]=1,res=(ans|((1ll<<w)-1ll));
for(int i=1;i<=n;i++)
{
for(int j=1;j<=min(b,i);j++)
{
for(int k=j-1;k<=i-1;k++)
{
if(!dp[k][j-1]) continue;
if((res|(s[i]-s[k]))!=res) continue;
dp[i][j]=true; break;
}
}
}
bool sign=false;
for(int i=a;i<=b;i++)
if(dp[n][i]==true) sign=true;
if(!sign) ans|=(1ll<<w);
}
printf("%lld",ans);
}
}
namespace work
{
void main()
{
for(int w=45;w>=0;w--)
{
memset(f,0x3f3f3f,sizeof(f));
f[0]=0,res=(ans|((1ll<<w)-1ll));
for(int i=1;i<=n;i++)
{
for(int k=0;k<i;k++)
{
if((res|(s[i]-s[k]))!=res) continue;
f[i]=min(f[i],f[k]+1);
}
}
if(f[n]>b) ans|=(1ll<<w);
}
printf("%lld",ans);
}
}
signed main()
{
n=read(),a=read(),b=read();
for(int i=1;i<=n;i++)
s[i]=s[i-1]+read();
if(a==1) work::main();
else solve::main();
return 0;
}
[NOI 2015] 寿司晚宴
状压神题,将小质因子(前八个质因子)压成状态,剩下的大质因子另存起来,然后开三个相同的 \(dp\) 数组随便维护
AC code
点击查看代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<cmath>
#include<algorithm>
#define int long long
using namespace std;
const int maxn=510;
inline int read()
{
int w=0,f=1;
char ch=getchar();
while(ch<'0' || ch>'9')
{
if(ch=='-') f=-1;
ch=getchar();
}
while(ch>='0' && ch<='9')
{
w=(w<<3)+(w<<1)+(ch^48);
ch=getchar();
}
return w*f;
}
int prime[10]={0,2,3,5,7,11,13,17,19},ans,n,p;
int dp[maxn][maxn],f1[maxn][maxn],f2[maxn][maxn];
struct num
{
int val,S=0,big=-1;
void init()
{
for(int i=1;i<=8;i++)
{
if(val%prime[i]) continue;
S|=(1<<i-1);
while(val%prime[i]==0) val/=prime[i];
}
if(val!=1) big=val;
}
}a[maxn];
bool cmp(num a,num b)
{
return a.big<b.big;
}
signed main()
{
n=read(),p=read();
for(int i=2;i<=n;i++)
a[i-1].val=i,a[i-1].init();
sort(a+1,a+n,cmp);
dp[0][0]=1;
for(int i=1;i<n;i++)
{
if(i==1 || a[i].big!=a[i-1].big || a[i].big==-1)
memcpy(f1,dp,sizeof(f1)),memcpy(f2,dp,sizeof(f2));
for(int j=255;j>=0;j--)
{
for(int k=255;k>=0;k--)
{
if(k&j) continue;
if((k&a[i].S)==0) f1[j|a[i].S][k]=(f1[j|a[i].S][k]+f1[j][k])%p;
if((j&a[i].S)==0) f2[j][k|a[i].S]=(f2[j][k|a[i].S]+f2[j][k])%p;
}
}
if(i==n-1 || a[i].big!=a[i+1].big || a[i].big==-1)
{
for(int j=0;j<=255;j++)
{
for(int k=0;k<=255;k++)
{
if(k&j) continue;
dp[j][k]=(f1[j][k]+f2[j][k]-dp[j][k]+p)%p;
}
}
}
}
for(int j=0;j<=255;j++)
{
for(int k=0;k<=255;k++)
{
if(k&j) continue;
ans=(ans+dp[j][k])%p;
}
}
printf("%lld",ans);
return 0;
}
[Ynoi 2017] 由乃的玉米田
第一道Ynoi祭!根号分治,前三种操作上个月已经做过了,主要是第四种,\(x\geq \sqrt n\) 莫队直接维护了就行,否则对于每个 \(x\) 找到对于每个 \(i\) 来说最近的满足条件的值然后判断是不是在区间内即可
AC code
点击查看代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<vector>
#include<cmath>
#include<bitset>
#include<algorithm>
using namespace std;
const int maxn=1e5+5;
inline int read()
{
int w=0,f=1;
char ch=getchar();
while(ch<'0' || ch>'9')
{
if(ch=='-') f=-1;
ch=getchar();
}
while(ch>='0' && ch<='9')
{
w=(w<<3)+(w<<1)+(ch^48);
ch=getchar();
}
return w*f;
}
int n,m,N;
int val[maxn];
int pre[maxn];
int cnt[maxn];
int ans[maxn];
int bel[maxn];
int near[maxn];
int siz,num,sum;
bitset <maxn> liv;
bitset <maxn> rev;
struct que
{
int id,l,r,opt,x;
}q[maxn];
vector <que> ask[maxn];
bool cmp(que a,que b)
{
if(bel[a.l]!=bel[b.l])
return bel[a.l]<bel[b.l];
else
return a.r<b.r;
}
void add(int x)
{
if(cnt[val[x]]==0)
liv[val[x]]=rev[N-val[x]]=1;
cnt[val[x]]++;
}
void del(int x)
{
cnt[val[x]]--;
if(cnt[val[x]]==0)
liv[val[x]]=rev[N-val[x]]=0;
}
int main()
{
n=read(),m=read();
for(int i=1;i<=n;i++) val[i]=read();
N=maxn; siz=sqrt(n),num=ceil((double)n/siz);
for(int i=1;i<=num;i++)
for(int j=(i-1)*siz+1;j<=min(n,i*siz);j++) bel[j]=i;
for(int i=1;i<=m;i++)
{
int opt,l,r,x;
opt=read(),l=read();
r=read(),x=read();
if(opt==4 && x<siz)
ask[x].push_back({i,l,r,opt,x});
else q[++sum]=que{i,l,r,opt,x};
}
sort(q+1,q+sum+1,cmp);
int l=1,r=0;
for(int i=1;i<=sum;i++)
{
while(l<q[i].l) del(l++);
while(l>q[i].l) add(--l);
while(r<q[i].r) add(++r);
while(r>q[i].r) del(r--);
int opt=q[i].opt,x=q[i].x;
if(opt==1) ans[q[i].id]=(liv&(liv<<x)).any();
if(opt==2) ans[q[i].id]=(liv&(rev>>(N-x))).any();
if(opt==3)
{
for(int k=1;k*k<=x;k++)
{
if(x%k) continue;
ans[q[i].id]=(liv[k]&&liv[x/k]);
if(ans[q[i].id]) break;
}
}
if(opt==4)
{
for(int k=1;k*x<=n;k++)
if(liv[k] && liv[k*x]) {ans[q[i].id]=true;break;}
}
}
for(int i=1;i<sqrt(n);i++)
{
if(ask[i].empty()) continue;
memset(pre,0,sizeof(pre));
memset(near,0,sizeof(near));
int l=0;
for(int j=1;j<=n;j++)
{
pre[val[j]]=j;
if(val[j]*i<=N) l=max(l,pre[val[j]*i]);
if(val[j]%i==0) l=max(l,pre[val[j]/i]);
near[j]=l;
}
for(int k=0;k<ask[i].size();k++)
ans[ask[i][k].id]=(ask[i][k].l<=near[ask[i][k].r]);
}
for(int i=1;i<=m;i++)
ans[i] ? puts("yuno") : puts("yumi");
return 0;
}
[WF 2013] Matryoshka
区间DP,对于每个小区间合并为一个完好的套娃集,设 \(dp_{i,j}\) 表示合并区间 \(i,j\) 的最小代价,最后在开一个DP数组合并所有套娃集的答案,判断合不合法以及计算贡献可以通过预处理信息来搞
AC code
点击查看代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<climits>
#include<cmath>
#include<algorithm>
using namespace std;
const int maxn=510;
const int INF=1e9;
inline int read()
{
int w=0,f=1;
char ch=getchar();
while(ch<'0' || ch>'9')
{
if(ch=='-') f=-1;
ch=getchar();
}
while(ch>='0' && ch<='9')
{
w=(w<<3)+(w<<1)+(ch^48);
ch=getchar();
}
return w*f;
}
int max_val[maxn][maxn];
int min_val[maxn][maxn];
bool vis[maxn],can[maxn][maxn];
int n,a[maxn],maxm,dp[maxn][maxn];
int salr[maxn][maxn][maxn],ans[maxn];
int main()
{
while(scanf("%d",&n)==1)
{
for(int i=1;i<=n;i++)
a[i]=read(),maxm=max(maxm,a[i]);
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
dp[i][j]=INF;can[i][j]=true;
max_val[i][j]=0;min_val[i][j]=INF;
}
}
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
for(int k=1;k<=500;k++) salr[i][j][k]=0;
for(int i=1;i<=n;i++) dp[i][i]=0;
for(int l=1;l<=n;l++)
{
for(int r=l;r<=n;r++)
{
for(int k=1;k<=500;k++) vis[k]=false;
for(int k=l;k<=r;k++)
{
max_val[l][r]=max(max_val[l][r],a[k]);
min_val[l][r]=min(min_val[l][r],a[k]);
for(int m=a[k]+1;m<=maxm;m++)
{
salr[l][r][m]++;
}
if(vis[a[k]]) can[l][r]=false;
vis[a[k]]=true;
}
}
}
for(int len=1;len<=n;len++)
{
for(int l=1;l+len-1<=n;l++)
{
int r=l+len-1;
if(!can[l][r]) continue;
for(int k=l;k+1<=r;k++)
{
if(min_val[l][k]<min_val[k+1][r])
{
dp[l][r]=min(dp[l][r],dp[l][k]+dp[k+1][r]+len-salr[l][k][min_val[k+1][r]]);
}
else
{
dp[l][r]=min(dp[l][r],dp[l][k]+dp[k+1][r]+len-salr[k+1][r][min_val[l][k]]);
}
}
}
}
for(int i=1;i<=n;i++) ans[i]=INF;
ans[0]=0;
for(int l=1;l<=n;l++)
{
for(int r=l;r<=n;r++)
{
if(max_val[l][r]==r-l+1 && can[l][r])
{
ans[r]=min(ans[r],ans[l-1]+dp[l][r]);
}
}
}
if(ans[n]==INF) cout<<"impossible"<<endl;
else cout<<ans[n]<<endl;
}
return 0;
}
[APIO 2014] 连珠线
换根DP(通过换根把所有蓝线都转变为父-我-子的类型),然后设 \(dp_{i,0/1}\) 表示节点 \(i\) 作为/不作为蓝线中点时的最大得分,然后按套路换根
AC code
点击查看代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<cmath>
#include<algorithm>
using namespace std;
const int maxn=200010;
inline int read()
{
int w=0,f=1;
char ch=getchar();
while(ch<'0' || ch>'9')
{
if(ch=='-') f=-1;
ch=getchar();
}
while(ch>='0' && ch<='9')
{
w=(w<<3)+(w<<1)+(ch^48);
ch=getchar();
}
return w*f;
}
int n,tot,ans;
int head[maxn];
int dp[maxn][2];
int max_val[maxn][2];
struct edge
{
int to,next,val;
}e[maxn*2];
void add(int x,int y,int z)
{
e[++tot].to=y;
e[tot].val=z;
e[tot].next=head[x];
head[x]=tot;
}
void dfs1(int x,int fa)
{
for(int i=head[x];i;i=e[i].next)
{
int to=e[i].to;if(to==fa){continue;}dfs1(to,x);
dp[x][0]+=max(dp[to][0],dp[to][1]+e[i].val);
dp[x][1]+=max(dp[to][0],dp[to][1]+e[i].val);
int val=dp[to][0]+e[i].val-max(dp[to][0],dp[to][1]+e[i].val);
if(val>max_val[x][0]) max_val[x][1]=max_val[x][0],max_val[x][0]=val;
else if(val>max_val[x][1]) max_val[x][1]=val;
}
dp[x][1]+=max_val[x][0];
}
void dfs2(int x,int fa)
{
int val; ans=max(ans,dp[x][0]);
int dp0=dp[x][0],dp1=dp[x][1];
int NO_1=max_val[x][0],NO_2=max_val[x][1];
for(int i=head[x];i;i=e[i].next)
{
int to=e[i].to; if(to==fa) continue;
dp[x][0]-=max(dp[to][0],dp[to][1]+e[i].val);
dp[x][1]-=max(dp[to][0],dp[to][1]+e[i].val);
val=dp[to][0]+e[i].val-max(dp[to][0],dp[to][1]+e[i].val);
if(val==max_val[x][0]) dp[x][1]+=max_val[x][1]-val;
dp[to][0]+=max(dp[x][0],dp[x][1]+e[i].val);
dp[to][1]+=max(dp[x][0],dp[x][1]+e[i].val);
dp[to][1]-=max_val[to][0];
val=dp[x][0]+e[i].val-max(dp[x][0],dp[x][1]+e[i].val);
if(val>max_val[to][0]) max_val[to][1]=max_val[to][0],max_val[to][0]=val;
else if(val>max_val[to][1]) max_val[to][1]=val;
dp[to][1]+=max_val[to][0];
dfs2(to,x);
dp[x][0]=dp0,dp[x][1]=dp1;max_val[x][0]=NO_1,max_val[x][1]=NO_2;
}
}
int main()
{
n=read();
for(int i=1;i<n;i++)
{
int u=read();
int v=read();
int w=read();
add(u,v,w),add(v,u,w);
}
memset(max_val,0xcf,sizeof(max_val));
dfs1(1,0),dfs2(1,0); printf("%d",ans);
return 0;
}
Intervals
人类智慧DP,虽说是套路题,设 \(dp_{i,j}\) 表示第 \(i\) 个位置,上一个0在 \(j\) 的最大得分
然后在区间右端点时讨论贡献
线段树优化一下(直接把 \(dp\) 值放在线段树上)
AC code
点击查看代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<cmath>
#include<vector>
#include<algorithm>
#define int long long
using namespace std;
const int maxn=2e5+5;
inline int read()
{
int w=0,f=1;
char ch=getchar();
while(ch<'0' || ch>'9')
{
if(ch=='-') f=-1;
ch=getchar();
}
while(ch>='0' && ch<='9')
{
w=(w<<3)+(w<<1)+(ch^48);
ch=getchar();
}
return w*f;
}
int n,m;
struct s_t
{
int l,r;
int tag;
int val;
}t[maxn*4];
struct que
{
int l,r,v;
}q[maxn];
vector <que> w[maxn];
void build(int p,int l,int r)
{
t[p].l=l,t[p].r=r;
if(l==r) return ;
int mid=(l+r)>>1;
build(p*2,l,mid);
build(p*2+1,mid+1,r);
}
void push_down(int p)
{
if(t[p].tag)
{
t[p*2].val+=t[p].tag;
t[p*2+1].val+=t[p].tag;
t[p*2].tag+=t[p].tag;
t[p*2+1].tag+=t[p].tag;
t[p].tag=0;
}
}
void update(int p,int l,int r,int k)
{
if(l<=t[p].l && t[p].r<=r)
{
t[p].val+=k;
t[p].tag+=k;
return ;
}
push_down(p);
int mid=(t[p].l+t[p].r)>>1;
if(l<=mid) update(p*2,l,r,k);
if(r>mid) update(p*2+1,l,r,k);
t[p].val=max(t[p*2].val,t[p*2+1].val);
}
signed main()
{
n=read(),m=read();
for(int i=1;i<=m;i++)
{
q[i].l=read();
q[i].r=read();
q[i].v=read();
w[q[i].r].push_back(q[i]);
}
build(1,1,n);
for(int i=1;i<=n;i++)
{
update(1,i,i,max(t[1].val,0ll));
for(int j=0;j<w[i].size();j++)
{
update(1,w[i][j].l,w[i][j].r,w[i][j].v);
}
}
cout<<max(t[1].val,0ll);
return 0;
}
陌上开花
三维偏序板题,第一维sort掉,然后CDQ内部将区间 \(l,mid\) 与 \(mid+1,r\) 分别按第二维sort,树状数组搞定第三维
AC code
点击查看代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<cmath>
#include<algorithm>
using namespace std;
const int maxn=2e5+5;
inline int read()
{
int w=0,f=1;
char ch=getchar();
while(ch<'0' || ch>'9')
{
if(ch=='-') f=-1;
ch=getchar();
}
while(ch>='0' && ch<='9')
{
w=(w<<3)+(w<<1)+(ch^48);
ch=getchar();
}
return w*f;
}
int n,cnt,m,k;
int ans[maxn];
struct num
{
int a,b,c,sum,ans;
}val[maxn],cdq[maxn];
int t[maxn];
bool cmp1(num x,num y)
{
if(x.a==y.a)
if(x.b==y.b) return x.c<y.c;
else return x.b<y.b;
return x.a<y.a;
}
bool cmp2(num x,num y)
{
if(x.b==y.b)
return x.c<y.c;
return x.b<y.b;
}
int lowbit(int x)
{
return x&-x;
}
void update(int x,int y)
{
for(;x<=k;x+=lowbit(x)) t[x]+=y;
}
int query(int x)
{
int res=0;
for(;x;x-=lowbit(x)) res+=t[x];
return res;
}
void CDQ(int l,int r)
{
if(l==r) return ;
int mid=(l+r)>>1;
CDQ(l,mid),CDQ(mid+1,r);
int L=l;
sort(cdq+l,cdq+mid+1,cmp2);
sort(cdq+mid+1,cdq+r+1,cmp2);
for(int i=mid+1;i<=r;i++)
{
while(L<=mid && cdq[L].b<=cdq[i].b)
{
update(cdq[L].c,cdq[L].sum);L++;
}
cdq[i].ans+=query(cdq[i].c);
}
for(int i=l;i<L;i++)
{
update(cdq[i].c,-cdq[i].sum);
}
}
int main()
{
n=read(),k=read();
for(int i=1;i<=n;i++)
{
val[i].a=read();
val[i].b=read();
val[i].c=read();
}
sort(val+1,val+n+1,cmp1);int tot=0;
for(int i=1;i<=n;i++)
{
tot++;
if(val[i].a!=val[i+1].a || val[i].b!=val[i+1].b || val[i].c!=val[i+1].c)
cdq[++m]=val[i],cdq[m].sum=tot,tot=0;
}
CDQ(1,m);
for(int i=1;i<=m;i++)
ans[cdq[i].ans+cdq[i].sum-1]+=cdq[i].sum;
for(int i=0;i<n;i++)
cout<<ans[i]<<endl;
return 0;
}
天使玩偶
细节居多的CDQ,感觉题解写的无法理解就按自己想的写了写,然后样例不过,大方向还是对的,细节上处理了一下就A了(拆成左上、左下、右上、右下四个方向二维数点,然后CDQ统计左区间对右区间的影响)
(CDQ内二维数点时排序要用归并,否则会被卡常,因此果断sort+\(O_2\))
AC code
点击查看代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<cmath>
#include<climits>
#include<algorithm>
using namespace std;
const int INF=1e9;
const int maxn=1e6+5;
inline int read()
{
int w=0,f=1;
char ch=getchar();
while(ch<'0' || ch>'9')
{
if(ch=='-') f=-1;
ch=getchar();
}
while(ch>='0' && ch<='9')
{
w=(w<<3)+(w<<1)+(ch^48);
ch=getchar();
}
return w*f;
}
int n,m,cnt;
int t[maxn],lim;
struct que
{
int x,y,num;
int ans,opt;
}q[maxn],Q[maxn];
int lowbit(int x)
{ return x&-x; }
bool cmp(que a,que b)
{
if(a.x==b.x)
return a.y<b.y;
return a.x<b.x;
}
void clear(int x)
{ for(;x<lim;x+=lowbit(x)) t[x]=-INF; }
void update(int x,int y)
{ for(;x<lim;x+=lowbit(x)) t[x]=max(t[x],y); }
int query(int x)
{
int res=-INF;
for(;x;x-=lowbit(x)) res=max(res,t[x]);
return res;
}
void CDQ(int l,int r)
{
if(l==r) return ;
int mid=(l+r)>>1,tot=0;
CDQ(l,mid),CDQ(mid+1,r);
for(int i=l;i<=r;i++)
{
if(i<=mid && q[i].opt==1)
Q[++tot].x=q[i].x,Q[tot].y=q[i].y,Q[tot].num=i;
if(i>mid && q[i].opt==2)
Q[++tot].x=q[i].x,Q[tot].y=q[i].y,Q[tot].num=i;
}
sort(Q+1,Q+tot+1,cmp);
for(int i=1;i<=tot;i++)
{
if(q[Q[i].num].opt==1)
{
update(lim-Q[i].y,Q[i].x-Q[i].y);
}
else
{
q[Q[i].num].ans=min(q[Q[i].num].ans,abs(Q[i].x-Q[i].y-query(lim-Q[i].y)));
}
}
for(int i=1;i<=tot;i++)
if(q[Q[i].num].opt==1) clear(lim-Q[i].y);
for(int i=1;i<=tot;i++)
{
if(q[Q[i].num].opt==1)
{
update(Q[i].y,Q[i].x+Q[i].y);
}
else
{
q[Q[i].num].ans=min(q[Q[i].num].ans,abs(Q[i].x+Q[i].y-query(Q[i].y)));
}
}
for(int i=1;i<=tot;i++)
if(q[Q[i].num].opt==1) clear(Q[i].y);
for(int i=tot;i>=1;i--)
{
if(q[Q[i].num].opt==1)
{
update(lim-Q[i].y,-Q[i].x-Q[i].y);
}
else
{
q[Q[i].num].ans=min(q[Q[i].num].ans,abs(-Q[i].x-Q[i].y-query(lim-Q[i].y)));
}
}
for(int i=tot;i>=1;i--)
if(q[Q[i].num].opt==1) clear(lim-Q[i].y);
for(int i=tot;i>=1;i--)
{
if(q[Q[i].num].opt==1)
{
update(Q[i].y,Q[i].y-Q[i].x);
}
else
{
q[Q[i].num].ans=min(q[Q[i].num].ans,abs(Q[i].y-Q[i].x-query(Q[i].y)));
}
}
for(int i=tot;i>=1;i--)
if(q[Q[i].num].opt==1) clear(Q[i].y);
}
int main()
{
n=read(),m=read();
for(int i=1;i<=n;i++)
{
q[++cnt].opt=1;
q[cnt].x=read();
q[cnt].y=read()+1;
lim=max(lim,q[cnt].y);
}
for(int i=1;i<=m;i++)
{
q[++cnt].opt=read();
q[cnt].x=read();
q[cnt].y=read()+1;
q[cnt].ans=INF;
lim=max(lim,q[cnt].y);
}
lim++;
memset(t,0xcf,sizeof(t));
CDQ(1,n+m);
for(int i=n;i<=n+m;i++)
{
if(q[i].opt==2)
cout<<q[i].ans<<'\n';
}
return 0;
}
[CEOI 2017] Building Bridges
CDQ维护斜率优化板题,因为 \(k0\) 与 \(x\) 均不单调,我们先按 \(k0\) sort,然后再CDQ归并使局部的 \(x\) 单调,最后单调队列转移
AC code
点击查看代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<cmath>
#include<algorithm>
#define int long long
using namespace std;
const int INF=1e18;
const int maxn=2e5+5;
inline int read()
{
int w=0,f=1;
char ch=getchar();
while(ch<'0' || ch>'9')
{
if(ch=='-') f=-1;
ch=getchar();
}
while(ch>='0' && ch<='9')
{
w=(w<<3)+(w<<1)+(ch^48);
ch=getchar();
}
return w*f;
}
int h[maxn];
int w[maxn];
int dp[maxn],n;
int sum[maxn];
struct node
{
int x,y,k,id;
}nod[maxn],buc[maxn];
bool cmp(node a,node b)
{
return a.k<b.k;
}
int q[maxn];
long double slope(int i,int j)
{
if(nod[i].x==nod[j].x)
return nod[j].y>nod[i].y ? INF : -INF ;
return (long double)(nod[j].y-nod[i].y)/(nod[j].x-nod[i].x);
}
void CDQ(int l,int r)
{
if(l==r)
{
int id=nod[l].id;
nod[l].y=dp[id]+h[id]*h[id]-sum[id];
return ;
}
int mid=(l+r)>>1;
int L=l,R=mid+1,head=1,tail=0;
for(int i=l;i<=r;i++)
{
if(nod[i].id<=mid)
buc[L++]=nod[i];
else
buc[R++]=nod[i];
}
for(int i=l;i<=r;i++) nod[i]=buc[i];
CDQ(l,mid);
for(int i=l;i<=mid;i++)
{
while(head<tail && slope(q[tail-1],q[tail])>=slope(q[tail],i)) tail--;
q[++tail]=i;
}
for(int i=mid+1;i<=r;i++)
{
while(head<tail && slope(q[head],q[head+1])<=nod[i].k) head++;
if(head<=tail)
{
int id=nod[i].id,j=q[head];
dp[id]=min(dp[id],nod[j].y-nod[i].k*nod[j].x+sum[id-1]+h[id]*h[id]);
}
}
CDQ(mid+1,r);
L=l,R=mid+1;int cnt=l;
while(L<=mid && R<=r)
{
if(nod[L].x<nod[R].x)
buc[cnt++]=nod[L++];
else
buc[cnt++]=nod[R++];
}
while(L<=mid) buc[cnt++]=nod[L++];
while(R<=r) buc[cnt++]=nod[R++];
for(int i=l;i<=r;i++) nod[i]=buc[i];
}
signed main()
{
n=read();
for(int i=1;i<=n;i++)
{
h[i]=read();
}
for(int i=1;i<=n;i++)
{
w[i]=read(),nod[i].id=i;
sum[i]=sum[i-1]+w[i];dp[i]=INF;
nod[i].x=h[i],nod[i].k=h[i]*2;
}
sort(nod+1,nod+n+1,cmp);
dp[1]=0,CDQ(1,n);
printf("%lld",dp[n]);
return 0;
}
[CQOI 2011] 动态逆序对
把时间维度加上,然后就变成了三维偏序
AC code
点击查看代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<cmath>
#include<algorithm>
#define int long long
using namespace std;
const int maxn=1e5+10;
inline int read()
{
int w=0,f=1;
char ch=getchar();
while(ch<'0' || ch>'9')
{
if(ch=='-') f=-1;
ch=getchar();
}
while(ch>='0' && ch<='9')
{
w=(w<<3)+(w<<1)+(ch^48);
ch=getchar();
}
return w*f;
}
int n,m,tot;
int val[maxn];
int del[maxn];
int ans[maxn];
int pos[maxn];
int t[maxn];
struct num
{
int val,pos,id,opt;
}q[maxn*2];
bool cmp(num a,num b)
{
return a.pos<b.pos;
}
int lowbit(int x)
{ return x&-x; }
void update(int x,int y)
{ for(;x<=n;x+=lowbit(x)) t[x]+=y; }
int query(int x)
{
int res=0;
for(;x;x-=lowbit(x)) res+=t[x];
return res;
}
void CDQ(int l,int r)
{
if(l==r) return ;
int mid=(l+r)>>1,L=l;
CDQ(l,mid),CDQ(mid+1,r);
sort(q+l,q+mid+1,cmp);
sort(q+mid+1,q+r+1,cmp);
for(int i=mid+1;i<=r;i++)
{
while(L<=mid && q[L].pos<=q[i].pos)
{
update(q[L].val,q[L].opt);L++;
}
ans[q[i].id]+=q[i].opt*(query(n)-query(q[i].val));
}
for(int i=l;i<L;i++)
{
update(q[i].val,-q[i].opt);
}
L=mid;
for(int i=r;i>=mid+1;i--)
{
while(L>=l && q[L].pos>=q[i].pos)
{
update(q[L].val,q[L].opt);L--;
}
ans[q[i].id]+=q[i].opt*query(q[i].val-1);
}
for(int i=mid;i>L;i--)
{
update(q[i].val,-q[i].opt);
}
}
signed main()
{
n=read(),m=read();
for(int i=1;i<=n;i++)
{
val[i]=read();
pos[val[i]]=i;
q[++tot].val=val[i];
q[tot].pos=i,q[tot].opt=1;
}
for(int i=1;i<=m;i++)
{
del[i]=read();
q[++tot].val=del[i];
q[tot].pos=pos[del[i]];
q[tot].id=i,q[tot].opt=-1;
}
CDQ(1,tot);
for(int i=1;i<=m;i++)
{
ans[i]+=ans[i-1];
}
for(int i=0;i<m;i++)
{
cout<<ans[i]<<'\n';
}
return 0;
}
[HNOI 2010] 城市建设
动态最小生成树板子,在CDQ的过程中维护一下一定会在最终的最小生成树中的边有哪些,将它们缩成一个点,再维护一下不需要的边有哪些,删除这些边,从而减小图的规模,最后直接上kruskal板子就行
AC code
点击查看代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<ctime>
#include<cmath>
#include<algorithm>
#define ll long long
using namespace std;
const int LOG=20;
const int INF=1e9;
const int maxn=5e4+5;
inline int read()
{
int w=0,f=1;
char ch=getchar();
while(ch<'0' || ch>'9')
{
if(ch=='-') f=-1;
ch=getchar();
}
while(ch>='0' && ch<='9')
{
w=(w<<3)+(w<<1)+(ch^48);
ch=getchar();
}
return w*f;
}
int n,m,k;
int val[maxn];
int num[LOG];
int fa[maxn];
int id[maxn];
struct edge
{
int u,v;
int val,pos;
}e[LOG][maxn],q1[maxn],q2[maxn];
struct que
{
ll ans;
int pos,val;
}q[maxn];
int find(int x)
{
if(fa[x]==x) return x;
return fa[x]=find(fa[x]);
}
void merge(int x,int y)
{
srand(time(0));
if(rand()&1) fa[x]=y;
else fa[y]=x;
}
bool cmp(edge a,edge b)
{
return a.val<b.val;
}
void construction(int &tot,ll &ans)
{
int cnt=0;
for(int i=1;i<=tot;i++)
fa[q1[i].u]=q1[i].u,fa[q1[i].v]=q1[i].v;
sort(q1+1,q1+tot+1,cmp);
for(int i=1;i<=tot;i++)
{
int u=find(q1[i].u);
int v=find(q1[i].v);
if(u==v) continue;
merge(u,v);q2[++cnt]=q1[i];
}
for(int i=1;i<=cnt;i++)
fa[q2[i].u]=q2[i].u,fa[q2[i].v]=q2[i].v;
for(int i=1;i<=cnt;i++)
{
int u=find(q2[i].u);
int v=find(q2[i].v);
if(u!=v && q2[i].val!=-INF)
merge(u,v),ans+=q2[i].val;
}
cnt=0;
for(int i=1;i<=tot;i++)
{
int u=q1[i].u,v=q1[i].v;
if(find(u)==find(v)) continue;
q2[++cnt]=q1[i];id[q1[i].pos]=cnt;
q2[cnt].u=fa[q2[cnt].u];
q2[cnt].v=fa[q2[cnt].v];
}
for(int i=1;i<=cnt;i++) q1[i]=q2[i];
tot=cnt;
}
void reduction(int &tot)
{
int cnt=0;
for(int i=1;i<=tot;i++)
fa[q1[i].u]=q1[i].u,fa[q1[i].v]=q1[i].v;
sort(q1+1,q1+tot+1,cmp);
for(int i=1;i<=tot;i++)
{
int u=q1[i].u,v=q1[i].v;
if(find(u)!=find(v))
{
merge(find(u),find(v));
q2[++cnt]=q1[i],id[q1[i].pos]=cnt;
}
else if(q1[i].val==INF)
q2[++cnt]=q1[i],id[q1[i].pos]=cnt;
}
for(int i=1;i<=cnt;i++) q1[i]=q2[i];
tot=cnt;
}
void CDQ(int l,int r,int dep,ll ans)
{
int tot=num[dep];
if(l==r) val[q[l].pos]=q[l].val;
for(int i=1;i<=tot;i++)
e[dep][i].val=val[e[dep][i].pos];
for(int i=1;i<=tot;i++)
q1[i]=e[dep][i],id[q1[i].pos]=i;
if(l==r)
{
q[l].ans=ans;
for(int i=1;i<=tot;i++)
fa[q1[i].u]=q1[i].u,fa[q1[i].v]=q1[i].v;
sort(q1+1,q1+tot+1,cmp);
for(int i=1;i<=tot;i++)
{
int u=find(q1[i].u);
int v=find(q1[i].v);
if(u==v) continue;
merge(u,v);q[l].ans+=q1[i].val;
}
return ;
}
for(int i=l;i<=r;i++) q1[id[q[i].pos]].val=-INF;
construction(tot,ans);
for(int i=l;i<=r;i++) q1[id[q[i].pos]].val=INF;
reduction(tot);
for(int i=1;i<=tot;i++) e[dep+1][i]=q1[i];
num[dep+1]=tot; int mid=(l+r)>>1;
CDQ(l,mid,dep+1,ans); CDQ(mid+1,r,dep+1,ans);
}
int main()
{
n=read(),m=read(),k=read();
for(int i=1;i<=m;i++)
{
e[0][i].pos=i;
e[0][i].u=read();
e[0][i].v=read();
e[0][i].val=read();
val[i]=e[0][i].val;
}
for(int i=1;i<=k;i++)
{
q[i].pos=read();
q[i].val=read();
}
num[0]=m;
CDQ(1,k,0,0);
for(int i=1;i<=k;i++)
cout<<q[i].ans<<'\n';
return 0;
}