一揽子计划
咕了!
liaoliao的BZOJ题表,一题题做(虽然有些题做过)。。
但是靖爷都说这些题没有意思,没有营养%%%
完成量(21/139)
BZOJ1226: [SDOI2009]学校食堂Dining
因为每个人的B值不超过7,所以上一个吃饭的人应该在这个人前面不超过7,后面也不超过7的位置上
状压DP,设f[i][j][k]为前i-1个人都已经吃完饭了,第i到i+7个人的状态为j,最后一个吃饭的人为第i+k个人
然后就可以转移
对于j&1==1,就是说第i个人已经吃了饭了,那么f[i+1][j>>1][k-1]=f[i][j][k],两者的意义是一样的
对于j&1==0,那么就枚举应当作为最后一个吃饭的人的位置i+p,f[i][j^(1<<p)][p]=f[i][j][k]+cal(i+k,i+p))
然后因为k有可能为负,所以整体的k+8存入数组
#include<cstdio> #include<cstring> #include<cstdlib> #include<algorithm> #include<cmath> using namespace std; int T[1100],B[1100]; int f[1100][310][21]; int cal(int x,int y){return (x==0||y==0)?0:T[x]^T[y];} int main() { int cas; scanf("%d",&cas); while(cas--) { int n; scanf("%d",&n); for(int i=1;i<=n;i++) scanf("%d%d",&T[i],&B[i]); memset(f,63,sizeof(f)); f[1][0][7]=0; for(int i=1;i<=n;i++) { for(int j=0;j<(1<<8);j++) { for(int k=0;k<=15;k++) { if(f[i][j][k]==f[0][0][0]) continue; if(j&1) f[i+1][j>>1][k-1]=min(f[i+1][j>>1][k-1],f[i][j][k]); else { int mmax=1<<30; for(int p=0;p<=7;p++) { if((j&(1<<p))==0) { if(i+p>mmax) break; mmax=min(mmax,i+p+B[i+p]); f[i][j^(1<<p)][p+8]=min(f[i][j^(1<<p)][p+8],f[i][j][k]+cal(i+k-8,i+p)); } } } } } } int ans=1<<30; for(int k=0;k<=8;k++) ans=min(f[n+1][0][k],ans); printf("%d\n",ans); } return 0; }
BZOJ1260: [CQOI2007]涂色paint
之前做过,还是很容易做得出来
区间DP,转移的话
f[i][j]表示i到j染成目标木板的i到j的最少涂色次数
转移方程有两个:
1.当st[i]==st[j]时,f[i][j]=min(min(f[i][j-1],f[i+1][j]),f[i+1][j-1]+1),因为如果左右两端都为同样颜色,那么第一次就将整块木板涂成同样颜色即可以做到
2.常规找断点,f[i][j]=min(f[i][j],f[i][k]+f[k+1][j])
#include<cstdio> #include<cstring> #include<algorithm> #include<cmath> #include<cstdlib> using namespace std; int f[51][51]; char st[51]; int main() { scanf("%s",st+1);int n=strlen(st+1); memset(f,63,sizeof(f)); for(int i=1;i<=n;i++) f[i][i]=1; for(int k=2;k<=n;k++) { for(int i=1;i+k-1<=n;i++) { if(st[i]==st[i+k-1]) f[i][i+k-1]=min(min(f[i][i+k-2],f[i+1][i+k-1]),f[i+1][i+k-2]+1); for(int j=i;j<i+k-1;j++) { f[i][i+k-1]=min(f[i][i+k-1],f[i][j]+f[j+1][i+k-1]); } } } printf("%d\n",f[1][n]); return 0; }
BZOJ1296: [SCOI2009]粉刷匠
一开始看错题了,还以为可以竖着粉刷
先枚举每一块木板,然后在这块木板上求f[i][j],表示这块木板上前i个格子粉刷j次所能得到的最多正确数
然后对ans[i]表示所有木板粉刷i次所能得到的最多正确数,把f[i][j]插入ans数组中做背包就行了
#include<cstdio> #include<cstring> #include<cstdlib> #include<algorithm> #include<cmath> #define INF 1<<30 using namespace std; char st[51][51]; int f[51][51];//f[i][j]表示当前木板的前i个刷k次所能得到的最多正确数 int mx[51]; int sum[51]; int ans[3100];//ans[i]表示所有木板粉刷i次最多正确数 int main() { int n,m,T; scanf("%d%d%d",&n,&m,&T); for(int i=1;i<=n;i++) scanf("%s",st[i]+1); memset(ans,-63,sizeof(ans)); ans[0]=0;int s=0; for(int p=1;p<=n;p++) { memset(f,-63,sizeof(f)); sum[0]=0; for(int i=1;i<=m;i++) sum[i]=sum[i-1]+st[p][i]-'0'; for(int i=0;i<=m;i++) f[i][0]=0; for(int i=1;i<=min(m,T);i++) { mx[i]=-(1<<30); for(int j=i;j<=m;j++) { for(int k=i-1;k<j;k++) { if(f[k][i-1]==f[0][1]) continue; f[j][i]=max(f[j][i],f[k][i-1]+max(j-k-sum[j]+sum[k],sum[j]-sum[k])); } mx[i]=max(f[j][i],mx[i]); } } for(int j=T;j>=1;j--) { for(int i=1;i<=min(j,m);i++) { if(ans[j-i]!=ans[T+1]) ans[j]=max(ans[j-i]+mx[i],ans[j]),s=max(ans[j],s); } } } printf("%d\n",s); return 0; }
BZOJ1560: [JSOI2009]火星藏宝图
结论贪心+DP
因为每个点的固定收益为正,而且两点的距离是欧几里得距离的平方,所以可以得到一个结论:
假设要从A->C,如果能A->B->C,肯定A->B->C是更优的
那我们就将点先按x为第一关键字,y为第二关键字排序
设f[i]为从第1个点到第i个点的最大收益,显然我们转移的时候只需找到前i-1个点所在每一列的最下面的点就可以了
这样复杂度就是O(mn),感觉卡过去了
#include<cstdio> #include<cstring> #include<cstdlib> #include<algorithm> #include<cmath> using namespace std; struct sit{int x,y,v;}p[210000]; bool cmp(sit n1,sit n2){return n1.x==n2.x?n1.y<n2.y:n1.x<n2.x;} int f[210000],pos[1100]; int dis(sit n1,sit n2){return (n1.x-n2.x)*(n1.x-n2.x)+(n1.y-n2.y)*(n1.y-n2.y);} int main() { int n,m; scanf("%d%d",&n,&m); for(int i=1;i<=n;i++) scanf("%d%d%d",&p[i].x,&p[i].y,&p[i].v); sort(p+1,p+n+1,cmp); memset(f,-63,sizeof(f)); f[1]=p[1].v;pos[1]=1; int x=1; for(int i=2;i<=n;i++) { for(int j=1;j<=p[i].y;j++) { if(pos[j]!=0) { f[i]=max(f[i],f[pos[j]]-dis(p[pos[j]],p[i])); } } f[i]+=p[i].v; pos[p[i].y]=i; } printf("%d\n",f[n]); return 0; }
BZOJ1806: [Ioi2007]Miners 矿工配餐
直接DP就好了
设f[i][t1][t2][t3][t4]为当前已经送了i个食物,且最近两次给第一个矿洞送了第t1和t2种食物(t1比t2更早送),给第二个矿洞送了第t3和t4种食物(t3比t4更早送)能得到的最大收益
假如t1,t2,t3,t4为0,表示当前没有送那么多的餐
转移想想就会了
然后这题卡空间。。没关系,滚动数组一下就好了
#include<cstdio> #include<cstring> #include<cstdlib> #include<algorithm> #include<cmath> using namespace std; char st[110000]; int f[2][4][4][4][4]; int a[110000]; int main() { int n; scanf("%d",&n); scanf("%s",st+1); for(int i=1;i<=n;i++) { if(st[i]=='M') a[i]=1; if(st[i]=='F') a[i]=2; if(st[i]=='B') a[i]=3; } memset(f,-1,sizeof(f)); f[0][0][0][0][0]=0; int now=0,last=1; int ans=0; for(int i=1;i<=n;i++) { now^=1;last^=1; for(int t1=0;t1<=3;t1++) { for(int t2=0;t2<=3;t2++) { int d1=0; if(t1!=0&&t2==0) { d1=1;if(t1!=a[i]) d1=2; } else if(t1==0&&t2!=0) { d1=1;if(t2!=a[i]) d1=2; } else if(t1!=0&&t2!=0) { if(t1==t2) { d1=1;if(t1!=a[i]) d1=2; } else { d1=2;if(t1!=a[i]&&t2!=a[i]) d1=3; } } else d1=1; for(int t3=0;t3<=3;t3++) { for(int t4=0;t4<=3;t4++) { int d2=0; if(t3!=0&&t4==0) { d2=1;if(t4!=a[i]) d2=2; } else if(t3==0&&t4!=0) { d2=1;if(t4!=a[i]) d2=2; } else if(t3!=0&&t4!=0) { if(t3==t4) { d2=1;if(t3!=a[i]) d2=2; } else { d2=2;if(t3!=a[i]&&t4!=a[i]) d2=3; } } else d2=1; if(f[last][t1][t2][t3][t4]!=-1) { f[now][t2][a[i]][t3][t4]=max(f[now][t2][a[i]][t3][t4],f[last][t1][t2][t3][t4]+d1); f[now][t1][t2][t4][a[i]]=max(f[now][t1][t2][t4][a[i]],f[last][t1][t2][t3][t4]+d2); ans=max(ans,max(f[now][t2][a[i]][t3][t4],f[now][t1][t2][t4][a[i]])); } } } } } } printf("%d\n",ans); return 0; }
BZOJ2121: 字符串游戏
这个DP有点神了。。
因为最后选择删除的多个区间,要么是包含关系,要么就相离关系,不可能存在区间相交的情况
所以设f1[l][r][i][j]为l到r所构成的字符串删掉若干区间后是否能够刚好构成第i个子串的前j个字符
f2[l][r]表示能否将l到r所构成的字符串都删掉
转移就要么就往后加一个字符,要么就找前面后面是否能够继承就行了
O(|L|3*|S|*|p|),竟然是能过的复杂度。。
#include<cstdio> #include<cstring> #include<cstdlib> #include<cmath> #include<algorithm> #define mes(x,y) memset(x,y,sizeof(x)) using namespace std; bool f1[210][210][41][31],f2[210][210]; char st[210]; char cc[41][31]; int h[151],len[210]; int main() { scanf("%s",st+1); len[0]=strlen(st+1); int n;scanf("%d",&n); for(int i=1;i<=n;i++) scanf("%s",cc[i]+1),len[i]=strlen(cc[i]+1); mes(f1,false);mes(f2,false); for(int i=1;i<=len[0];i++) { for(int j=1;j<=n;j++) { if(cc[j][1]==st[i]) f1[i][i][j][1]=true; if(f1[i][i][j][len[j]]==true) f2[i][i]=true; } } for(int k=2;k<=len[0];k++) { for(int l=1;l<=len[0]-k+1;l++) { int r=l+k-1; for(int i=1;i<=n;i++) { for(int j=1;j<=min(len[i],k);j++) { if(st[r]==cc[i][j]) f1[l][r][i][j]|=f1[l][r-1][i][j-1]; for(int p=l;p<=r;p++) { if(f2[l][p]==true) f1[l][r][i][j]|=f1[p+1][r][i][j]; if(f2[p][r]==true) f1[l][r][i][j]|=f1[l][p-1][i][j]; } } if(f1[l][r][i][len[i]]==true) f2[l][r]=true; } } } mes(h,63);h[0]=0; for(int i=1;i<=len[0];i++) { h[i]=h[i-1]+1; for(int j=1;j<=i;j++) if(f2[j][i]==true) h[i]=min(h[i],h[j-1]); } printf("%d\n",h[len[0]]); return 0; }
BZOJ2302: [HAOI2011]Problem c
其实想想就会发现与顺序是没有什么关系的
因为当一种方案成立,肯定会使得所有编号<=i的人数会>=i,这个想想应该就能懂
然后就可以写DP方程,设f[i][j]为有j个人的编号<=i的方案数
转移就组合数一下就行了,对于无解的情况就先判断掉
#include<cstdio> #include<cstdlib> #include<cstring> #include<cmath> #include<algorithm> #define INF 1<<30 #define mes(x,y) memset(x,y,sizeof(x)) #define Maxn 310 using namespace std; typedef long long LL; LL Mod; LL mul(LL a,LL b){return a*b%Mod;} LL add(LL a,LL b){return (a+b)%Mod;} LL p_mod(LL a,LL b) { LL ans=1; while(b!=0) { if(b%2==1) ans=mul(ans,a); a=mul(a,a);b>>=1; } return ans; } int s[Maxn]; LL f[Maxn][Maxn];//f[i][j]表示有j个人的编号<=i的方案数,显然j>=i LL C[Maxn][Maxn]; int main() { int T; scanf("%d",&T); while(T--) { int n,m;scanf("%d%d%lld",&n,&m,&Mod); mes(C,0);C[0][0]=1; for(int i=1;i<=300;i++) { C[i][0]=1; for(int j=1;j<=i;j++) { C[i][j]=add(C[i-1][j-1],C[i-1][j]); } } mes(s,0);s[0]=n-m; int p,q; for(int i=1;i<=m;i++) scanf("%d%d",&p,&q),s[q]++; bool bk=false; for(int i=1;i<=n;i++) { s[i]+=s[i-1]; if(s[i]<i){bk=true;break;} } if(bk==true){printf("NO\n");continue;} printf("YES "); mes(f,0);f[0][0]=1; for(int i=1;i<=n;i++) { for(int j=i;j<=s[i];j++) { int d=s[i]-s[i-1]; for(int k=d;k<=j-i+1;k++) { f[i][j]=add(f[i][j],mul(f[i-1][j-k],C[s[i]-d-j+k][k-d])); } } } printf("%lld\n",f[n][n]); } return 0; }
BZOJ2466: [中山市选2009]树
这个树形DP还是很好想的
设f[x][i]表示第x个点为i状态时所需要的最少操作数
对于状态0表示没按按钮且没亮,1表示没按按钮且亮了,2表示按了按钮且没亮,3表示按了按钮且亮了
然后在保证子节点一定亮的基础上转移就好了
#include<cstdio> #include<cstring> #include<cstdlib> #include<cstdio> #include<algorithm> #define mes(x,y) memset(x,y,sizeof(x)) #define Maxn 110 #define INF 1<<30 using namespace std; typedef long long LL; struct node{int x,y,next;}a[Maxn*2];int len,last[Maxn]; void ins(int x,int y){a[++len]=(node){x,y,last[x]};last[x]=len;} int f[Maxn][5],n; void dfs(int x,int fa) { int f1[4];f[x][0]=0;f[x][3]=1;f[x][1]=f[x][2]=n+1; for(int k=last[x];k;k=a[k].next) { int y=a[k].y; if(y==fa) continue; dfs(y,x); f1[0]=f[x][0];f1[1]=f[x][1];f1[2]=f[x][2];f1[3]=f[x][3]; f[x][0]=min(f1[0]+f[y][1],f1[1]+f[y][3]); f[x][1]=min(f1[1]+f[y][1],f1[0]+f[y][3]); f[x][2]=min(f1[2]+f[y][0],f1[3]+f[y][2]); f[x][3]=min(f1[3]+f[y][0],f1[2]+f[y][2]); } } int main() { while(scanf("%d",&n)!=EOF) { if(n==0) break; int x,y; len=0;mes(last,0); for(int i=1;i<n;i++) scanf("%d%d",&x,&y),ins(x,y),ins(y,x); dfs(1,0); printf("%d\n",min(f[1][1],f[1][3])); } return 0; }
BZOJ1787: [Ahoi2008]Meet 紧急集合
做过
转化为求三个点的LCA
发现三个点两两之间的LCA中必定有两个相同,而且其中不同的那个就是三个点的LCA
然后直接做就好了
#include<cstdio> #include<cstring> #include<algorithm> #include<cmath> #include<cstdlib> using namespace std; struct node { int x,y,next; }a[1100000];int len,last[510000]; int f[510000][25],dep[510000]; void ins(int x,int y) { len++; a[len].x=x;a[len].y=y; a[len].next=last[x];last[x]=len; } int bin[25]; void dfs(int x,int fa) { dep[x]=dep[fa]+1; f[x][0]=fa; for(int i=1;bin[i]<=dep[x];i++) f[x][i]=f[f[x][i-1]][i-1]; for(int k=last[x];k;k=a[k].next) { int y=a[k].y; if(y==fa) continue; dfs(y,x); } } int LCA(int x, int y) { if(dep[x]<dep[y]) swap(x,y); for(int i=20;i>=0;i--) if(dep[x]-dep[y]>=bin[i]) x=f[x][i]; if(x==y) return x; for(int i=20;i>=0;i--) if(dep[x]>=(1<<i)&&f[x][i]!=f[y][i]) x=f[x][i],y=f[y][i]; return f[x][0]; } int n,m; int dis(int x,int y) { int lca=LCA(x,y); return dep[x]+dep[y]-2*dep[lca]; } int main() { scanf("%d%d",&n,&m); bin[0]=1; for(int i=1;i<=20;i++) bin[i]=bin[i-1]<<1; len=0;memset(last,0,sizeof(last)); for(int i=1;i<n;i++) { int x,y; scanf("%d%d",&x,&y); ins(x,y);ins(y,x); } dfs(1,0); for(int i=1;i<=m;i++) { int x,y,z; scanf("%d%d%d",&x,&y,&z); int l1=LCA(x,y),l2=LCA(x,z),l3=LCA(y,z); if(l1==l2) printf("%d %d\n",l3,dis(x,l3)+dis(y,l3)+dis(z,l3)); else if(l1==l3) printf("%d %d\n",l2,dis(x,l2)+dis(y,l2)+dis(z,l2)); else if(l2==l3) printf("%d %d\n",l1,dis(x,l1)+dis(y,l1)+dis(z,l1)); } return 0; }
BZOJ1202: [HNOI2005]狡猾的商人
带权并查集就好了,判断是否合法
#include<cstdio> #include<cstring> #include<cstdlib> #include<algorithm> #include<cmath> using namespace std; int fa[110],v[110]; int findfa(int x) { if(fa[x]==x) return x; int f=fa[x]; fa[x]=findfa(fa[x]); v[x]+=v[f]; return fa[x]; } int main() { int T; scanf("%d",&T); while(T--) { memset(v,0,sizeof(v)); int n,m; scanf("%d%d",&n,&m); for(int i=0;i<=n;i++) fa[i]=i; bool bk=true; for(int i=1;i<=m;i++) { int x,y,k; scanf("%d%d%d",&x,&y,&k); if(bk==false) continue; int fx=findfa(x-1),fy=findfa(y); if(fx==fy) { if(v[y]-v[x-1]!=k) { bk=false;continue; } } else { fa[fy]=fx; v[fy]=k-v[y]+v[x-1]; } } if(bk==false) printf("false\n"); else printf("true\n"); } return 0; }
BZOJ2006: [NOI2010]超级钢琴
实际上就是求前k大子段和
暴力做就是枚举每个左端点然后枚举右端点
然而我们可以预处理前缀和,用RMQ就能够得到每个点为左端点时的最优右端点
可以发现以当前点为左端点的次优点一定不是右端点
那么我们就用优先队列一个个取,设四元组(p,x,y,d)为当前以p点为左端点,在x到y中选择最优右端点,得到的最大子段和为d
每次取出队顶的时候,将队顶分为(p,x,j-1,d1),(p,j+1,y,d2)j为以p点为左端点,在x到y中的最优右端点
而d1,d2就不用说了,就这样取k次就好了
#include<cstdio> #include<cstring> #include<cstdlib> #include<algorithm> #include<cmath> #include<queue> #define mes(x,y) memset(x,y,sizeof(x)) #define Maxn 510000 using namespace std; typedef long long LL; struct node { int p,l,r;LL d; friend bool operator < (node n1,node n2){return n1.d<n2.d;} }; priority_queue<node> q; LL a[Maxn],f[Maxn][21]; int Log[Maxn],p[Maxn][21]; LL mx(int l,int r) { int t=Log[r-l+1]; return max(f[l][t],f[r-(1<<t)+1][t]); } int P(int l,int r) { int t=Log[r-l+1]; if(f[l][t]>f[r-(1<<t)+1][t]) return p[l][t]; else return p[r-(1<<t)+1][t]; } int main() { int n,k,L,R; scanf("%d%d%d%d",&n,&k,&L,&R); for(int i=1;i<=n;i++) scanf("%lld",&a[i]),a[i]+=a[i-1],f[i][0]=a[i],p[i][0]=i; Log[0]=-1;for(int i=1;i<=n;i++) Log[i]=Log[i/2]+1; for(int i=1;i<=20;i++) { for(int j=1;j<=n-(1<<i)+1;j++) { if(f[j][i-1]>f[j+(1<<(i-1))][i-1]) f[j][i]=f[j][i-1],p[j][i]=p[j][i-1]; else f[j][i]=f[j+(1<<(i-1))][i-1],p[j][i]=p[j+(1<<(i-1))][i-1]; } } for(int i=1;i<=n-L+1;i++) { int x=i+L-1,y=min(i+R-1,n); q.push((node){i,x,y,mx(x,y)-a[i-1]}); } LL ans=0; for(int i=1;i<=k;i++) { node tno=q.top();q.pop(); int p=P(tno.l,tno.r); if(tno.l<=p-1) q.push((node){tno.p,tno.l,p-1,mx(tno.l,p-1)-a[tno.p-1]}); if(p+1<=tno.r) q.push((node){tno.p,p+1,tno.r,mx(p+1,tno.r)-a[tno.p-1]}); ans+=tno.d; } printf("%lld\n",ans); return 0; }
BZOJ2957: 楼房重建
对于一个楼房被看到,可以看作它与(0,0)的连线的斜率比前面所有的斜率都大
那么我们可以把每次操作都当成单点修改,然后求整段区间从第一个楼房开始斜率递增所得到的楼房数
设mx为每个区间中最大的斜率,c为每个区间从左端点开始能看到的楼房数
显然我们需要在修改的时候维护线段树
对于一段区间,显然左子区间的c值的贡献一定全部在整段区间的c值中,所以我们只要对右子区间进行处理
如果当前左子区间的最大值>=右子区间的最大值,那么右子区间对整段区间是没有贡献的
不然则在右子区间中找出以第一个大于左子区间的最大值的楼房,然后贡献为从这个楼房往后得到的楼房数(包括这个楼房)
就这样,注意一下精度就可以了
#include<cstdio> #include<cstring> #include<algorithm> #include<cmath> #include<cstdlib> #define eps 1e-10 using namespace std; struct trnode { int l,r,lc,rc,c; double mx; }tr[210000];int trlen; void bt(int l,int r) { int now=++trlen; tr[now].l=l;tr[now].r=r; tr[now].lc=tr[now].rc=-1; tr[now].c=0;tr[now].mx=0.0; if(l<r) { int mid=(l+r)/2; tr[now].lc=trlen+1;bt(l,mid); tr[now].rc=trlen+1;bt(mid+1,r); } } int ans; void findd(int now,double d) { if(tr[now].l==tr[now].r){ans++;return ;} int lc=tr[now].lc,rc=tr[now].rc; if(tr[lc].mx<=d) findd(rc,d); else ans+=tr[now].c-tr[lc].c,findd(lc,d); } void follow(int now) { int lc=tr[now].lc,rc=tr[now].rc; tr[now].c=tr[lc].c;tr[now].mx=tr[lc].mx; if(tr[rc].mx-tr[lc].mx>eps) { tr[now].mx=tr[rc].mx; ans=0;findd(rc,tr[lc].mx); tr[now].c+=ans; } } void change(int now,int x,double d) { if(tr[now].l==tr[now].r) { tr[now].mx=d; tr[now].c=1; return ; } int lc=tr[now].lc,rc=tr[now].rc,mid=(tr[now].l+tr[now].r)/2; if(x<=mid) change(lc,x,d); else change(rc,x,d); follow(now); } int main() { int n,m; scanf("%d%d",&n,&m); trlen=0;bt(1,n); for(int i=1;i<=m;i++) { int x,y; scanf("%d%d",&x,&y); change(1,x,double(y)/double(x)); printf("%d\n",tr[1].c); } return 0; }
BZOJ3289: Mato的文件管理
莫队,考虑加数减数时,树状数组求逆序对数对答案的影响即可
#include<cstdio> #include<cstring> #include<cstdlib> #include<algorithm> #include<cmath> using namespace std; typedef long long LL; struct LS { int d,id; }s[51000]; struct qn { int l,r,id; LL d; }q[51000]; int bk[51000]; bool cmp(qn n1,qn n2) { return bk[n1.l]==bk[n2.l]?n1.r<n2.r:bk[n1.l]<bk[n2.l]; } bool cmpd(qn n1,qn n2) { return n1.id<n2.id; } bool lsd(LS n1,LS n2) { return n1.d<n2.d; } bool lsid(LS n1,LS n2) { return n1.id<n2.id; } LL a[51000]; int lowbit(int x){return x&-x;} int n; LL getsum(int x) { LL ans=0; while(x!=0) { ans+=a[x]; x-=lowbit(x); } return ans; } void change(int x,LL c) { while(x<=n) { a[x]+=c; x+=lowbit(x); } } LL t[51000]; int main() { scanf("%d",&n); int block=int(sqrt(n)); for(int i=1;i<=n;i++) { scanf("%d",&s[i].d); s[i].id=i; bk[i]=(i-1)/block+1; } sort(s+1,s+n+1,lsd); for(int i=1;i<=n;i++) s[i].d=i; sort(s+1,s+n+1,lsid); int m; scanf("%d",&m); for(int i=1;i<=m;i++) { scanf("%d%d",&q[i].l,&q[i].r); q[i].id=i; } sort(q+1,q+m+1,cmp); int l=1,r=0; LL ans=0; memset(a,0,sizeof(a)); for(int i=1;i<=m;i++) { while(l<q[i].l) { if(l>r){l++;continue;} ans-=(r-l+1)-getsum(s[l].d); change(1,-1); if(s[l].d!=n) change(s[l].d+1,1); l++; } while(l>q[i].l) { if(l>r+1){l--;continue;} ans+=(r-l+1)-getsum(s[l-1].d); l--; change(1,1); if(s[l].d!=n) change(s[l].d+1,-1); } while(r<q[i].r) { if(l>r+1){r++;continue;} ans+=getsum(s[r+1].d); r++; change(1,1); if(s[r].d!=n) change(s[r].d+1,-1); } while(r>q[i].r) { if(l>r){r--;continue;} ans-=getsum(s[r].d)-1; change(1,-1); if(s[r].d!=n) change(s[r].d+1,1); r--; } q[i].d=ans; } sort(q+1,q+m+1,cmpd); for(int i=1;i<=m;i++) printf("%lld\n",q[i].d); return 0; }
BZOJ3242: [Noi2013]快餐店
显然是一棵基环树,而且对于一个最优快餐店的位置而言,它肯定有一条环上的边不会走
那么就枚举删边,然后在环上操作,求出相当于当前删边后的树的直径,求出最小的
然后再和原图上原来的子树的直径比较就行了
#include<cstdio> #include<cstring> #include<cstdlib> #include<cmath> #include<algorithm> #include<queue> #define mes(x,y) memset(x,y,sizeof(x)) #define Maxn 110000 #define INF 1LL<<62 using namespace std; struct node{int x,y,next;double d;}a[Maxn*2];int len,last[Maxn]; void ins(int x,int y,double d){a[++len]=(node){x,y,last[x],d};last[x]=len;} int dfn[Maxn],id,fa[Maxn]; int e[Maxn],cnt,p[Maxn]; bool in[Maxn]; void ph(int x) { dfn[x]=++id; for(int k=last[x];k;k=a[k].next) { int y=a[k].y; if(y==a[fa[x]].x) continue; if(dfn[y]!=0) { if(dfn[x]>dfn[y]) continue; e[++cnt]=((k-1)^1)+1;p[cnt]=x;in[x]=true; while(y!=x) e[++cnt]=fa[y],in[y]=true,p[cnt]=y,y=a[fa[y]].x; } else fa[y]=k,ph(y); } } double f1[Maxn],f2[Maxn],MX;//f1直径,f2链 void dfs(int x,int fa) { for(int k=last[x];k;k=a[k].next) { int y=a[k].y; if(y==fa||in[y]==true) continue; dfs(y,x); f1[x]=max(f1[x],f2[x]+f2[y]+a[k].d); MX=max(MX,f1[x]); f2[x]=max(f2[x],f2[y]+a[k].d); } } double s[Maxn]; double L[Maxn],l[Maxn],R[Maxn],r[Maxn]; double ml[Maxn],mr[Maxn]; int to[Maxn]; int main() { int n; scanf("%d",&n); len=0;mes(last,0); for(int i=1;i<=n;i++) { int x,y;double d; scanf("%d%d%lf",&x,&y,&d); ins(x,y,d);ins(y,x,d); } mes(in,false); cnt=id=0;ph(1); mes(f1,0);mes(f2,0); for(int i=1;i<=cnt;i++) dfs(p[i],0); reverse(e+1,e+cnt+1); to[1]=a[e[1]].x;s[1]=a[e[1]].d; for(int i=2;i<=cnt;i++) to[i]=a[e[i-1]].y,s[i]=a[e[i]].d; to[cnt+1]=to[1]; // int head=1,tail=1;list[1]=1; // for(int i=2;i<=cnt*2+1;i++) // { // while(head<=tail&&i-list[head]>=cnt) head++; // int x=list[head]; // if(i>=cnt) R[i]=f1[to[i]]+f1[to[x]]+s[i]-s[x]; // while(head<=tail&&(f1[to[i]]-s[i])>=(f1[to[list[tail]]]-s[list[tail]])) tail--; // list[++tail]=i; // } L[0]=l[0]=0; double sum=0,mx=0; // for(int i=1;i<=cnt+1;i++) printf("%d ",to[i]);printf("\n"); // for(int i=1;i<=cnt+1;i++) printf("%.1lf ",f2[to[i]]);printf("\n"); // printf("L\n"); for(int i=1;i<=cnt+1;i++) { sum+=s[i-1]; L[i]=max(L[i-1],mx+f2[to[i]]+sum); l[i]=max(l[i-1],sum+f2[to[i]]); mx=max(mx,f2[to[i]]-sum); // printf("%d: L:%.1lf l:%.1lf ml:%.1lf\n",i,L[i],l[i],ml[i]); } R[cnt+1]=r[cnt+1]=mx=0;sum=0; // printf("R\n"); for(int i=cnt;i>=1;i--) { sum+=s[i]; R[i]=max(R[i+1],mx+f2[to[i]]+sum); r[i]=max(r[i+1],sum+f2[to[i]]); mx=max(mx,f2[to[i]]-sum); // printf("%d: R:%.1lf r:%.1lf mr:%.1lf\n",i,R[i],r[i],mr[i]); } double ans=INF; for(int i=1;i<=cnt;i++) ans=min(ans,max(max(L[i],R[i+1]),l[i]+r[i+1])); printf("%.1lf\n",max(ans,MX)/2.0); return 0; }
BZOJ4170: 极光
将位置i以及权值d,当作是坐标(x,y),那么每次询问就是求与当前坐标的曼哈顿距离<=k的点数
显然不好求,那么就转换一下坐标系,将坐标转换为(x+y,x-y),将曼哈顿距离转换为切比雪夫距离
转换为矩阵之后就是CDQ分治裸题了
#include<cstdio> #include<cstring> #include<cstdlib> #include<cmath> #include<algorithm> #define mes(x,y) memset(x,y,sizeof(x)) #define Maxn 110000 using namespace std; struct ask { int tp,x,y,t,f;/*type px py anst 1/-1*/ friend bool operator < (ask n1,ask n2){return n1.x==n2.x?n1.tp<n2.tp:n1.x<n2.x;} }Q[Maxn*4],tmp[Maxn*4];//1为修改,2为询问 int a[Maxn*8]; int lowbit(int x){return x&-x;} void change(int x,int d) { while(x<=Maxn*4) { a[x]+=d; x+=lowbit(x); } } int getsum(int x) { int ans=0; while(x!=0) { ans+=a[x]; x-=lowbit(x); } return ans; } void clear(int x) { while(x!=0) { if(a[x]==0) break; a[x]=0;x+=lowbit(x); } } int ans[Maxn]; void CDQ(int l,int r) { if(l==r) return ; int mid=(l+r)/2; CDQ(l,mid);CDQ(mid+1,r); int p=l,q=mid+1,t=l; while(p<=mid&&q<=r) { if(Q[p]<Q[q]) { if(Q[p].tp==1) change(Q[p].y,1); tmp[t++]=Q[p++]; } else { if(Q[q].tp==2) ans[Q[q].t]+=Q[q].f*getsum(Q[q].y); tmp[t++]=Q[q++]; } } while(p<=mid) tmp[t++]=Q[p++]; while(q<=r) { if(Q[q].tp==2) ans[Q[q].t]+=Q[q].f*getsum(Q[q].y); tmp[t++]=Q[q++]; } for(int i=l;i<=r;i++) { if(i<=mid) clear(Q[i].y); Q[i]=tmp[i]; } } int nx[Maxn],ny[Maxn]; int main() { int n,q; scanf("%d%d",&n,&q); for(int i=1;i<=n;i++) { int d;scanf("%d",&d); Q[i]=(ask){1,i+d+Maxn,i-d+Maxn,0,0}; nx[i]=Q[i].x;ny[i]=Q[i].y; } int cnt=0,nn=n; for(int i=1;i<=q;i++) { char st[7];int x,d; scanf("%s%d%d",st+1,&x,&d); if(st[1]=='M') { Q[++n]=(ask){1,x+d+Maxn,x-d+Maxn,0,0}; nx[x]=Q[n].x;ny[x]=Q[n].y; } else { ++cnt; Q[++n]=(ask){2,min(Maxn*4,nx[x]+d),min(Maxn*4,ny[x]+d),cnt,1}; Q[++n]=(ask){2,max(nx[x]-d-1,1),min(Maxn*4,ny[x]+d),cnt,-1}; Q[++n]=(ask){2,min(Maxn*4,nx[x]+d),max(ny[x]-d-1,1),cnt,-1}; Q[++n]=(ask){2,max(nx[x]-d-1,1),max(ny[x]-d-1,1),cnt,1}; } } mes(a,0); CDQ(1,n); //for(int i=1;i<=n;i++) printf("%d %d %d %d\n",tmp[i].tp,tmp[i].x,tmp[i].y,tmp[i].nn);printf("\n"); for(int i=1;i<=cnt;i++) printf("%d\n",ans[i]); return 0; }
BZOJ3781: 小B的询问
莫队即可
#include<cstdio> #include<cstring> #include<cstdlib> #include<algorithm> #include<cmath> using namespace std; typedef long long LL; int a[51000]; struct qn { int l,r,id; LL d; }q[51000]; int bk[51000]; bool cmp(qn n1,qn n2) { return bk[n1.l]==bk[n2.l]?n1.r<n2.r:bk[n1.l]<bk[n2.l]; } bool cmpd(qn n1,qn n2) { return n1.id<n2.id; } LL sum[51000],ans; void update(int x,int ad) { ans-=sum[a[x]]*sum[a[x]]; sum[a[x]]+=ad; ans+=sum[a[x]]*sum[a[x]]; } int main() { int n,m,k; scanf("%d%d%d",&n,&m,&k); int block=int(sqrt(n)); for(int i=1;i<=n;i++) { scanf("%d",&a[i]); bk[i]=(i-1)/block+1; } for(int i=1;i<=m;i++) { scanf("%d%d",&q[i].l,&q[i].r); q[i].id=i; } sort(q+1,q+m+1,cmp); int l=1,r=0; ans=0; memset(sum,0,sizeof(sum)); for(int i=1;i<=m;i++) { while(l<q[i].l){update(l,-1);l++;} while(l>q[i].l){update(l-1,1);l--;} while(r<q[i].r){update(r+1,1);r++;} while(r>q[i].r){update(r,-1);r--;} q[i].d=ans; } sort(q+1,q+m+1,cmpd); for(int i=1;i<=m;i++) printf("%lld\n",q[i].d); return 0; }
BZOJ3809: Gty的二逼妹子序列
莫队即可
#include<cstdio> #include<cstring> #include<cstdlib> #include<algorithm> #include<cmath> using namespace std; struct node { int l,r,a,b,id,d; }q[1100000]; int s[110000]; int bl[1100],br[1100],belong[110000]; bool cmp1(node n1,node n2) { if(belong[n1.l]<belong[n2.l]) return true; if(belong[n1.l]>belong[n2.l]) return false; if(belong[n1.l]==belong[n2.l]) { if(n1.r<n2.r) return true; if(n1.r>n2.r) return false; } return false; } bool cmp2(node n1,node n2) { return n1.id<n2.id; } int d[1100],sum[110000]; void add(int x) { sum[x]++; if(sum[x]==1) d[belong[x]]++; } void del(int x) { sum[x]--; if(sum[x]==0) d[belong[x]]--; } int solve(int x,int y) { int bx=belong[x],by=belong[y]; int ans=0; if(bx==by) { for(int i=x;i<=y;i++) { if(sum[i]>0) ans++; } return ans; } for(int i=bx+1;i<=by-1;i++) ans+=d[i]; for(int i=x;i<=br[bx];i++) if(sum[i]>0) ans++; for(int i=bl[by];i<=y;i++) if(sum[i]>0) ans++; return ans; } int main() { int n,m; scanf("%d%d",&n,&m); for(int i=1;i<=n;i++) scanf("%d",&s[i]); int block=int(sqrt(n)); for(int i=1;i<=n;i++) { int t=(i-1)/block+1; belong[i]=t; if(bl[t]==0) br[t-1]=i-1,bl[t]=i; } br[belong[n]]=n; for(int i=1;i<=m;i++) { scanf("%d%d%d%d",&q[i].l,&q[i].r,&q[i].a,&q[i].b); q[i].id=i; } sort(q+1,q+m+1,cmp1); memset(sum,0,sizeof(sum)); memset(d,0,sizeof(d)); int l=1,r=0; for(int i=1;i<=m;i++) { while(l>q[i].l){l--;add(s[l]);} while(l<q[i].l){del(s[l]);l++;} while(r>q[i].r){del(s[r]);r--;} while(r<q[i].r){r++;add(s[r]);} q[i].d=solve(q[i].a,q[i].b); } sort(q+1,q+m+1,cmp2); for(int i=1;i<=m;i++) printf("%d\n",q[i].d); return 0; }
BZOJ3133: [Baltic2013]ballmachine
还是挺裸的
可以发现实际上一开始将所有球扔下去之后得到的序列是固定的,每次放球的时候,只要求出序列中最靠前的位置放球就行了
而且可以一个一个球放,因为输入合法,删的球数最多就是放的球数
对于删球,实际上求的就是当前位置向上的球数
直接用两个线段树,再树剖一下就行了
#include<cstdio> #include<cstring> #include<cstdlib> #include<algorithm> #include<cmath> #include<vector> #define mes(x,y) memset(x,y,sizeof(x)) #define Maxn 110000 using namespace std; vector<int> a[Maxn]; void ins(int x,int y){a[x].push_back(y);} int pt[Maxn],id,pre[Maxn],to[Maxn]; bool cmp(int x,int y){return pt[x]<pt[y];} struct node{int l,r,lc,rc,c;}tr[Maxn*2],p[Maxn*2];int trlen,plen; void bt(int t,int l,int r) { if(t==0) { int now=++trlen; tr[now]=(node){l,r,-1,-1,0}; if(l<r) { int mid=(l+r)/2; tr[now].lc=trlen+1;bt(0,l,mid); tr[now].rc=trlen+1;bt(0,mid+1,r); } } else { int now=++plen; p[now]=(node){l,r,-1,-1,0}; if(l<r) { int mid=(l+r)/2; p[now].lc=plen+1;bt(1,l,mid); p[now].rc=plen+1;bt(1,mid+1,r); } } } int getspace(int now) { if(tr[now].l==tr[now].r) return tr[now].l; int lc=tr[now].lc,rc=tr[now].rc; if(tr[lc].r-tr[lc].l+1>tr[lc].c) return getspace(lc); else return getspace(rc); } void inout(int now,int x,int d) { if(tr[now].l==tr[now].r){tr[now].c=d;return ;} int lc=tr[now].lc,rc=tr[now].rc,mid=(tr[now].l+tr[now].r)/2; if(x<=mid) inout(lc,x,d); else inout(rc,x,d); tr[now].c=tr[lc].c+tr[rc].c; } int tot[Maxn],son[Maxn],fa[Maxn],dep[Maxn]; void dfs1(int x)//pre_tree_node { son[x]=0;tot[x]=1; for(int i=0;i<a[x].size();i++) { int y=a[x][i]; fa[y]=x;dep[y]=dep[x]+1; dfs1(y); tot[x]+=tot[y]; if(tot[y]>tot[son[x]]) son[x]=y; pt[x]=min(pt[x],pt[y]); } } void dfs2(int x) { sort(a[x].begin(),a[x].end(),cmp); for(int i=0;i<a[x].size();i++) { int y=a[x][i]; dfs2(y); } pre[++id]=x;to[x]=id; } int top[Maxn],ys[Maxn],z,bk[Maxn]; void pre_tree_edge(int x,int tp) { ys[x]=++z;top[x]=tp;bk[z]=x; if(son[x]!=0) pre_tree_edge(son[x],tp); for(int i=0;i<a[x].size();i++) { int y=a[x][i]; if(y!=son[x]) pre_tree_edge(y,y); } } void change(int now,int x,int d) { if(p[now].l==p[now].r){p[now].c=d;return ;} int lc=p[now].lc,rc=p[now].rc,mid=(p[now].l+p[now].r)/2; if(x<=mid) change(lc,x,d); else change(rc,x,d); p[now].c=p[lc].c+p[rc].c; } int getsum(int now,int l,int r) { if(p[now].l==l&&p[now].r==r) return p[now].c; int lc=p[now].lc,rc=p[now].rc,mid=(p[now].l+p[now].r)/2; if(r<=mid) return getsum(lc,l,r); else if(l>mid) return getsum(rc,l,r); else return getsum(lc,l,mid)+getsum(rc,mid+1,r); } int sit,rt; int getsit(int x) { int tx=top[x],ans=0,pp; while(x!=0) { int d=getsum(1,ys[tx],ys[x]); ans+=d; if(d==ys[x]-ys[tx]+1) pp=tx,x=fa[tx],tx=top[x]; else { if(d!=0) sit=bk[ys[x]-d+1]; else sit=pp; return ans-1; } } sit=rt; return ans-1; } int main() { int n,Q; scanf("%d%d",&n,&Q); for(int i=1;i<=n;i++) { int f; scanf("%d",&f); if(f==0) rt=i; else ins(f,i); } for(int i=1;i<=n;i++) pt[i]=i; fa[rt]=dep[rt]=0;dfs1(rt); id=0;dfs2(rt); z=0;pre_tree_edge(rt,rt); trlen=0;bt(0,1,n); plen=0;bt(1,1,z); while(Q--) { int op,x,sp; scanf("%d%d",&op,&x); if(op==1) { for(int i=1;i<=x;i++) { sp=getspace(1); inout(1,sp,1); change(1,ys[pre[sp]],1); if(i==x) printf("%d\n",pre[sp]); } } else { printf("%d\n",getsit(x)); inout(1,to[sit],0); change(1,ys[sit],0); } } return 0; }
BZOJ4662: Snow
有个数组开小了,成功拍了两个下午,真开心。。
因为区间互不包含,而且左端点递增,所以右端点也是递增的
那么就先离散化,将所有都分成一段一段的
然后线段树维护每个人的清理区域,因为每一小段影响的人的编号也是一个区间
然后每次找到最小的人就暴力扫一遍这个人清理区间的所有小段,逐一修改
用并查集来加快扫,保证每个小段只会被扫一次就行了
一直以为自己处理每一小段的影响时有细节没做好才导致WA,结果是因为有一个数组没开好(怪不得小数据拍不出来
#include<cstdio> #include<cstdlib> #include<cmath> #include<algorithm> #include<cstring> #define Maxn 310000 #define mes(x,y) memset(x,y,sizeof(x)) #define INF 1LL<<62 using namespace std; typedef long long LL; struct LS{int x,z,id;}A[Maxn*4]; bool cmp1(LS n1,LS n2){return n1.x<n2.x;} bool cmp2(LS n1,LS n2){return n1.id<n2.id;} int to[Maxn*2]; struct node{int l,r,lc,rc,p;LL lz,c;}tr[Maxn*4];int trlen; int lx(int x){return A[2*x-1].x;} int rx(int x){return A[2*x].x;} int lz(int x){return A[2*x-1].z;} int rz(int x){return A[2*x].z;} void bt(int l,int r) { int now=++trlen; tr[now]=(node){l,r,-1,-1,0,0,INF}; if(l==r) tr[now].c=rx(l)-lx(l),tr[now].p=l; else { int mid=(l+r)/2; tr[now].lc=trlen+1;bt(l,mid); tr[now].rc=trlen+1;bt(mid+1,r); int lc=tr[now].lc,rc=tr[now].rc; tr[now].c=min(tr[lc].c,tr[rc].c); tr[now].p=tr[lc].c<=tr[rc].c?tr[lc].p:tr[rc].p; } } void update(int now) { int lc=tr[now].lc,rc=tr[now].rc; if(lc!=0) { tr[lc].c+=tr[now].lz; tr[lc].lz+=tr[now].lz; } if(rc!=0) { tr[rc].c+=tr[now].lz; tr[rc].lz+=tr[now].lz; } tr[now].lz=0; } void change(int now,int l,int r,LL c) { if(tr[now].l==l&&tr[now].r==r) { tr[now].c+=c; tr[now].lz+=c; return ; } int lc=tr[now].lc,rc=tr[now].rc,mid=(tr[now].l+tr[now].r)/2; if(tr[now].lz!=0) update(now); if(r<=mid) change(lc,l,r,c); else if(l>mid) change(rc,l,r,c); else change(lc,l,mid,c),change(rc,mid+1,r,c); tr[now].c=min(tr[lc].c,tr[rc].c); tr[now].p=tr[lc].c<=tr[rc].c?tr[lc].p:tr[rc].p; } int fa[Maxn*4]; int findfa(int x) { if(fa[x]!=x) fa[x]=findfa(fa[x]); return fa[x]; } int L[Maxn*4],R[Maxn*4]; int main() { // freopen("4662.in","r",stdin); // freopen("4662.out","w",stdout); int t,n; scanf("%d%d",&t,&n); for(int i=1;i<=n;i++) scanf("%d%d",&A[2*i-1].x,&A[2*i].x),A[2*i-1].id=2*i-1,A[2*i].id=2*i; sort(A+1,A+2*n+1,cmp1); int z=1;A[1].z=1;to[1]=A[1].x; for(int i=2;i<=2*n;i++) { if(A[i].x!=A[i-1].x) z++; A[i].z=z;to[z]=A[i].x; } sort(A+1,A+2*n+1,cmp2); trlen=0;bt(1,n); int p=1; for(int i=2;i<=z;i++) { if(lz(p)<=i-1&&i<=rz(p)) L[i]=p; else { while(p<=n&&(!(lz(p)<=i-1&&i<=rz(p)))) { p++; if(lz(p)>=i) break; } if(!(lz(p)<=i-1&&i<=rz(p))) L[i]=z+1; else L[i]=p; } } p=n; for(int i=z;i>=2;i--) { if(lz(p)<=i-1&&i<=rz(p)) R[i]=p; else { while(p>=1&&(!(lz(p)<=i-1&&i<=rz(p)))) { p--; if(rz(p)<=i-1) break; } if(!(lz(p)<=i-1&&i<=rz(p))) R[i]=0; else R[i]=p; } } for(int i=1;i<=z+1;i++) fa[i]=i; for(int i=1;i<=n;i++) { int sit=tr[1].p;printf("%d\n",sit); // if(tr[1].c<0) // { // printf("WA\n"); // return 0; // } int x=findfa(lz(sit)+1); while(x<=rz(sit)) { if(L[x]<=R[x]) change(1,L[x],R[x],-to[x]+to[x-1]); int y=findfa(x+1);fa[x]=y; x=y; } change(1,sit,sit,INF); } return 0; }
BZOJ1237: [SCOI2008]配对
贪心+DP即可
先将两个数组排序
随便画画图就大概可以知道,配对的两个数相对位置不会>2
所以设f[i]为配i对数的最小值,然后从i-2,i-1,i继承就行了
#include<cstdio> #include<cstdlib> #include<cstring> #include<algorithm> #include<cmath> #define Maxn 110000 #define mes(x,y) memset(x,y,sizeof(x)) using namespace std; typedef long long LL; int A[Maxn],B[Maxn]; LL f[Maxn]; int main() { int n; scanf("%d",&n); for(int i=1;i<=n;i++) scanf("%d%d",&A[i],&B[i]); sort(A+1,A+n+1);sort(B+1,B+n+1); if(n==1&&A[1]==B[1]){printf("-1\n");return 0;} memset(f,63,sizeof(f));f[0]=0; for(int i=1;i<=n;i++) { if(A[i]!=B[i]) f[i]=min(f[i],f[i-1]+abs(A[i]-B[i])); if(i>=2&&A[i-1]!=B[i]&&A[i]!=B[i-1]) f[i]=min(f[i],f[i-2]+abs(A[i-1]-B[i])+abs(A[i]-B[i-1])); if(i>=3) { //i-2-->i-1 i-1-->i i-->i-2 if(A[i-2]!=B[i-1]&&A[i-1]!=B[i]&&A[i]!=B[i-2]) f[i]=min(f[i],f[i-3]+abs(A[i-2]-B[i-1])+abs(A[i-1]-B[i])+abs(A[i]-B[i-2])); //i-1-->i-2 i-->i-1 i-2-->i if(A[i-1]!=B[i-2]&&A[i]!=B[i-1]&&A[i-2]!=B[i]) f[i]=min(f[i],f[i-3]+abs(A[i-1]-B[i-2])+abs(A[i]-B[i-1])+abs(A[i-2]-B[i])); //i-2-->i i-1-->i-1 i-->i-2 if(A[i-2]!=B[i]&&A[i-1]!=B[i-1]&&A[i]!=B[i-2]) f[i]=min(f[i],f[i-3]+abs(A[i-2]-B[i])+abs(A[i-1]-B[i-1])+abs(A[i]-B[i-2])); } } printf("%lld\n",f[n]); return 0; }
BZOJ1221: [HNOI2001] 软件开发
看题就很像费用流
这题关键在于毛巾能够重复用
现将每天拆成两个点,一个入点,一个出点
以下(x,y,c,d)表示x连向y,流量为c,费用为d
i表示入点,i+n表示出点
(st,i,n[i],0) (i+n,ed,n[i],0)相当于先给毛巾,然后再处理费用的问题
(i+n,i+a+1,inf,fa) (i+n,i+b+1,inf,fb)将出点连向能够A或B消毒后的那一天的入点
(i,i+1,inf,0)没用完的毛巾可以留到下一天
(st,i+n,INF,f)可以直接在这一天买毛巾
这样就能保证每天有n[i]条毛巾用,而且能循环利用了
#include<cstdio> #include<cstring> #include<cstdlib> #include<algorithm> #include<cmath> #include<queue> #define mes(x,y) memset(x,y,sizeof(x)) #define Maxn 1100 #define INF 1<<30 using namespace std; struct node{int x,y,c,d,next,other;}a[Maxn*200];int last[Maxn*2],len; void ins(int x,int y,int c,int d) { int k1=++len,k2=++len; a[k1].x=x;a[k1].y=y;a[k1].d=d;a[k1].c=c; a[k1].next=last[x];last[x]=k1; a[k2].x=y;a[k2].y=x;a[k2].d=-d;a[k2].c=0; a[k2].next=last[y];last[y]=k2; a[k1].other=k2; a[k2].other=k1; } int p[Maxn],st,ed; int d[Maxn*2],pos[Maxn*2]; bool v[Maxn*2]; queue<int> q; bool spfa() { memset(v,false,sizeof(v));v[st]=true; memset(d,63,sizeof(d));d[st]=0; q.push(st); bool bk=false; while(q.empty()==0) { int x=q.front();q.pop(); for(int k=last[x];k;k=a[k].next) { int y=a[k].y; if(a[k].c>0&&d[y]>d[x]+a[k].d) { d[y]=d[x]+a[k].d; pos[y]=k; if(v[y]==false) { v[y]=true; if(y==ed) bk=true; else q.push(y); } } } v[x]=false; } return bk; } int Flow() { int ans=0; while(spfa()) { int mmin=INF; for(int x=ed;x!=st;x=a[pos[x]].x) { mmin=min(a[pos[x]].c,mmin); } ans+=d[ed]*mmin; for(int x=ed;x!=st;x=a[pos[x]].x) { a[pos[x]].c-=mmin; a[a[pos[x]].other].c+=mmin; } } return ans; } int main() { int n,A,B,f,fa,fb; scanf("%d%d%d%d%d%d",&n,&A,&B,&f,&fa,&fb); for(int i=1;i<=n;i++) scanf("%d",&p[i]); len=0;mes(last,0); st=2*n+1;ed=2*n+2; for(int i=1;i<=n;i++) ins(st,i,p[i],0),ins(st,i+n,INF,f); for(int i=1;i<=n-A-1;i++) ins(i,i+n+A+1,INF,fa); for(int i=1;i<=n-B-1;i++) ins(i,i+n+B+1,INF,fb); for(int i=1;i<n;i++) ins(i,i+1,INF,0); for(int i=1;i<=n;i++) ins(i+n,ed,p[i],0); // printf("%d\n",len/2); // for(int i=1;i<=len;i+=2) printf("%d %d %d %d\n",a[i].x,a[i].y,a[i].c,a[i].d); printf("%d\n",Flow()); return 0; }