USACO 2011~2012 silv&&gold 解题报告
USACO 2011~2012 silv&&gold 解题报告
一共是六场比赛,官网上有题面,数据和题解:http://usaco.org/index.php?page=contests
壹
2011 November Contest Results.
SILVER:
T1 :Cow Beauty Pageant BFS
题意:给你一个地图,上面有三个连通块(标注为'X'),连通块是四连通的,求最少增加多少块'X'能使得这三个连通块连通。
分析:有两种可能1.从地图上任意一点向三个连通块连边
2.三个连通块互相两两连边
BFS处理出来连通块,之后枚举求代价最小即可
注意代价是通过曼哈顿距离求出来的,需要处理边界
代码:
#include <stdio.h> #include <string.h> #include <algorithm> using namespace std; int a[51][51],idx[4][2550],idy[4][2550],siz[4]; char map[51][51]; int n,m,vis[51][51]; int Q[5050],l,r; int tx[]={0,1,0,-1}; int ty[]={1,0,-1,0}; int ABS(int x){return x>0?x:-x;} int M(int x,int y,int xx,int yy) { return ABS(x-xx)+ABS(y-yy)-1; } void bfs(int i,int j,int id) { idx[id][++siz[id]]=i; idy[id][siz[id]]=j; l=r=0; Q[r++]=i;Q[r++]=j; vis[i][j]=1; while(l<r) { int x=Q[l++],y=Q[l++],k; vis[x][y]=1; for(k=0;k<4;k++) { int dx=x+tx[k],dy=y+ty[k]; if(dx>=1&&dx<=n&&dy>=1&&dy<=m&&!vis[dx][dy]&&map[dx][dy]=='X') { Q[r++]=dx;Q[r++]=dy; vis[dx][dy]=1; idx[id][++siz[id]]=dx; idy[id][siz[id]]=dy; } } } } int main() { scanf("%d%d",&n,&m); int i,j,k,cnt=1,tot=0; for(i=1;i<=n;i++) scanf("%s",map[i]+1); for(i=1;i<=n;i++) { for(j=1;j<=m;j++) { if(!vis[i][j]&&map[i][j]=='X') bfs(i,j,cnt++); } } int s1=1<<29,s2=1<<29,s3=1<<29; for(i=1;i<=siz[1];i++) { for(j=1;j<=siz[2];j++) { s1=min(s1,M(idx[1][i],idy[1][i],idx[2][j],idy[2][j])); } } for(i=1;i<=siz[2];i++) { for(j=1;j<=siz[3];j++) { s2=min(s2,M(idx[2][i],idy[2][i],idx[3][j],idy[3][j])); } } for(i=1;i<=siz[1];i++) { for(j=1;j<=siz[3];j++) { s3=min(s3,M(idx[1][i],idy[1][i],idx[3][j],idy[3][j])); } } int ans=min(min(s1+s2,s1+s3),s2+s3); for(i=1;i<=n;i++) { for(j=1;j<=m;j++) { int v1=1<<29,v2=1<<29,v3=1<<29; for(k=1;k<=siz[1];k++) v1=min(v1,M(i,j,idx[1][k],idy[1][k])); for(k=1;k<=siz[2];k++) v2=min(v2,M(i,j,idx[2][k],idy[2][k])); for(k=1;k<=siz[3];k++) v3=min(v3,M(i,j,idx[3][k],idy[3][k])); if(v1+v2+v3+1<ans) { ans=v1+v2+v3+1; } } } printf("%d\n",ans); }
T2 :Cow Lineup 尺取法
题意:依次给出N头牛的位置及种类,要求找出连续一段,使其中包含所有种类的牛,问:这连续的一段最小长度是多少?
分析:先把权值离散扔桶里,两个指针一点一点挪,最后取最短的一段合法即可。
代码:
#include <stdio.h> #include <string.h> #include <algorithm> using namespace std; #define N 50050 int n,h[N]; struct A{ int p,num,id,v; }a[N]; bool cmp1(const A &x,const A &y){return x.num<y.num;} //bool cmp2(const A &x,const A &y){return x.id<y.id;} bool cmp3(const A &x,const A &y){return x.p<y.p; } int main() { scanf("%d",&n); int i,j; for(i=1;i<=n;i++) scanf("%d%d",&a[i].p,&a[i].num),a[i].id=i; sort(a+1,a+n+1,cmp1);a[0].v=564564556; for(i=1,j=0;i<=n;i++){ if(a[i].num!=a[i-1].num)j++;a[i].v=j; } sort(a+1,a+n+1,cmp3); int tot=j,ok=0,cnt=0; //for(i=1;i<=n;i++) printf("%d %d\n",a[i].p,a[i].v); for(i=1;i<=n;i++){ if(!h[a[i].v]){ h[a[i].v]=1; cnt++; if(cnt==tot)break; } else h[a[i].v]++; } j=1; for(j=1;j<=i;j++) { h[a[j].v]--; if(h[a[j].v]<=0) { h[a[j].v]++; break; } } int ans=a[i].p-a[j].p; for(i++;i<=n;i++){ h[a[i].v]++; while(j<i) { h[a[j].v]--; if(h[a[j].v]<=0) { h[a[j].v]++; break; } j++; } ans=min(ans,a[i].p-a[j].p); } printf("%d\n",ans); }
T3 Tile Exchanging DP
题意:有N个正方形,依次给出它们的边长,要求可以换掉一些正方形:如果变长为A_i的正方形换成边长为B_i的正方形所需代价为:|A_i-B_i|*|A_i-B_i|。问至少花费多少代价使得这N个正方形面积总和为M?若无论如何也不可能使这N个正方形面积总和为M则输出-1。
分析:设f[i][j]为使得前i个正方形总面积为j的最小代价,f[i+1][j+k*k]=min(f[i+1][j+k*k],f[i][j]+(a[i+1]-k)*(a[i+1]-k)); 可能有无解的情况。
代码:
#include <stdio.h> #include <string.h> #include <algorithm> using namespace std; int f[20][10050],n,m,a[20]; int main() { scanf("%d%d",&n,&m); int i,j,k; for(i=1;i<=n;i++) scanf("%d",&a[i]); memset(f,0x3f,sizeof(f)); f[0][0]=0; for(i=0;i<n;i++){ for(j=0;j<=m;j++){ for(k=0;k*k+j<=m;k++){ f[i+1][j+k*k]=min(f[i+1][j+k*k],f[i][j]+(a[i+1]-k)*(a[i+1]-k)); } } } printf("%d",f[n][m]>100000000?-1:f[n][m]); }
GOLD
T1:Above the Median 树状数组
题意:给出一串数字,问中位数大于等于X的连续子串有几个。(这里如果有偶数个数,定义为偏大的那一个而非中间取平均)
分析:我们可以把大于X的设为1, 小于X的设为-1, 等于X的设为0。转化成求有几段子段的和大于等于0,求个前缀和然后树状数组维护就好
代码:
// luogu-judger-enable-o2 #include <stdio.h> #include <string.h> #include <algorithm> using namespace std; #define N 100050 #define LL long long int n,k,c[N]; struct A{ int num,v,id; }a[N]; bool cmp1(const A &x,const A &y){return x.num<y.num; } bool cmp2(const A &x,const A &y){return x.id<y.id; } int inq(int x) { int re=0; for(;x;x-=x&(-x))re+=c[x]; return re; } void fix(int x,int v) { for(;x<=n;x+=x&(-x))c[x]+=v; } int main() { scanf("%d%d",&n,&k); int i,x,j; for(i=1;i<=n;i++){ scanf("%d",&x); x=(x>=k?1:-1); a[i].num=a[i-1].num+x; a[i].id=i; } sort(a+1,a+n+1,cmp1);a[0].num=10004500; for(i=1,j=0;i<=n;i++) {if(a[i].num!=a[i-1].num)j++;a[i].v=j; } sort(a+1,a+n+1,cmp2); LL ans=0; for(i=1;i<=n;i++) { ans+=inq(a[i].v); fix(a[i].v,1); if(a[i].num>=0)ans++; } printf("%lld\n",ans); }
T2:Binary Sudoku 记忆化搜索
题意:给你一个二进制的数独,求最少改变几次能使得每一行每一列每个九宫格中1的个数都为偶数
分析:设f[r][c][sr][ss][p]为当前在r行c列,列的奇偶状态为sr(1<<9),这一行交叉的三个九宫格奇偶状态为ss,这一行的奇偶状态为p
之后从左到右逐行枚举,不合法的状态设为inf,向前推就行了
代码:
// luogu-judger-enable-o2 #include <stdio.h> #include <string.h> #include <algorithm> using namespace std; #define inf 100000000 int a[10][10]; int f[10][10][1<<9][1<<3][3]; //row/column/state of column/state of subgrid int dp(int r,int c,int sc,int ss,bool p) { if(r==10) return sc?inf:0; if(c==10) { if(p) return inf; if(!(r%3) && ss) return inf; return dp(r+1,1,sc,ss,0); } int &re = f[r][c][sc][ss][p]; if(~re) return re; re = !a[r][c] + dp(r,c+1,sc^(1<<c-1),ss^(1<<(c-1)/3),!p); re = min(re,a[r][c] + dp(r,c+1,sc,ss,p)); return re; } int main() { int i,j; for(i=1;i<=9;i++) for(j=1;j<=9;j++) scanf("%1d",&a[i][j]); memset(f,-1,sizeof(f)); printf("%d\n",dp(1,1,0,0,0)); }
T3:Cow Steeplechase 二分图匹配
题意:给出N平行于坐标轴的线段,要你选出尽量多的线段使得这些线段两两没有交点(顶点也算),横的与横的,竖的与竖的线段之间保证没有交点,输出最多能选出多少条线段。
分析:可以发现横线和竖线构成了一个二分图,有交点的线段连边,求二分图最大独立集即可。
最大独立集 = 总点数 - 最大匹配
代码:
#include <stdio.h> #include <string.h> #include <algorithm> #include <queue> using namespace std; #define N 300 #define M 180000 #define inf 10000000 #define S (n+1) #define T (n+2) struct A { int op,x,y,X,Y; }a[N]; int head[N],to[M],nxt[M],flow[M],cnt=1,n,dep[N]; void add(int u,int v,int f) { to[++cnt]=v; nxt[cnt]=head[u]; head[u]=cnt; flow[cnt]=f; } bool bfs() { queue<int>q; memset(dep,0,sizeof(dep)); dep[S]=1;q.push(S); while(!q.empty()) { int x=q.front();q.pop(); for(int i=head[x];i;i=nxt[i]) { if(!dep[to[i]]&&flow[i]) { dep[to[i]]=dep[x]+1; if(to[i]==T)return 1; q.push(to[i]); } } } return 0; } int dfs(int x,int mf) { if(x==T)return mf; int nf=0; for(int i=head[x];i;i=nxt[i]) { if(dep[to[i]]==dep[x]+1&&flow[i]) { int tmp=dfs(to[i],min(flow[i],mf-nf)); nf+=tmp; flow[i]-=tmp; flow[i^1]+=tmp; if(nf==mf)break; } } return nf; } void dinic() { int ans=n,f; while(bfs()) { while(f=dfs(S,inf)) { ans-=f; } } printf("%d",ans); } int main() { scanf("%d",&n); for(int i=1;i<=n;i++) { scanf("%d%d%d%d",&a[i].x,&a[i].y,&a[i].X,&a[i].Y); if(a[i].x==a[i].X) { a[i].op=1;add(S,i,1);add(i,S,0); if(a[i].y>a[i].Y)swap(a[i].y,a[i].Y); } else { a[i].op=2;add(i,T,1);add(T,i,0); if(a[i].x>a[i].X)swap(a[i].x,a[i].X); } } for(int i=1;i<=n;i++) { if(a[i].op==2)continue; for(int j=1;j<=n;j++) { if(i==j||a[j].op==1)continue; if(a[i].x>=a[j].x&&a[i].x<=a[j].X&&a[j].y>=a[i].y&&a[j].y<=a[i].Y) { add(i,j,inf); add(j,i,0); } } } dinic(); }
贰
2011 December Contest Results.
SILVER:
T1 : Cow Photography 金组弱化版
T2 : Roadblock 最短路
题意:有一个无向图,共N个节点,编号1至N,共M条边。FJ在节点1,它想到达节点N。FJ总是会选择最短路径到达节点N。作为捣蛋的奶牛Bessie,它想尽量延迟FJ到达节点N的时间,于是Bessie决定从M条边之中选择某一条边,使得改边的长度变成原来的两倍,由于智商的问题,Bessie不知道选择加倍哪条边的长度才能使得FJ到达N号节点的时间最迟。注意:不管Bessie选择加倍哪条边的长度,FJ总是会从1号节点开始走最短路径到达N号点。
分析:可以发现加倍的边一定是在最短路上的,枚举每条最短路上的边,把它倍长后再求一遍最短路,更新答案。
代码:
#include <stdio.h> #include <string.h> #include <algorithm> using namespace std; int map[260][260],f[260],n,m,Q[70020],l,r,vis[260]; int from[260]; void spfa() { memset(f,0x3f,sizeof(f)); memset(vis,0,sizeof(vis)); vis[Q[r++]=1]=1;f[1]=0; while(l<r) { int x=Q[l++];vis[x]=0; for(int i=1;i<=n;i++) { if(f[i]>f[x]+map[x][i]) { from[i]=x; f[i]=f[x]+map[x][i]; if(!vis[i]) { vis[i]=1; Q[r++]=i; } } } } } void sppfa() { memset(f,0x3f,sizeof(f)); memset(vis,0,sizeof(vis)); vis[Q[r++]=1]=1;f[1]=0; while(l<r) { int x=Q[l++];vis[x]=0; for(int i=1;i<=n;i++) { if(f[i]>f[x]+map[x][i]) { f[i]=f[x]+map[x][i]; if(!vis[i]) { vis[i]=1; Q[r++]=i; } } } } } int main() { scanf("%d%d",&n,&m); int x,y,z; memset(map,0x3f,sizeof(map)); while(m--) { scanf("%d%d%d",&x,&y,&z); if(x!=y) { map[x][y]=min(map[x][y],z); map[y][x]=map[x][y]; } } spfa(); int minn=f[n],maxn=0;; //printf("%d\n",f[5]); x=n; while(1) { map[x][from[x]]*=2; map[from[x]][x]*=2; sppfa(); maxn=max(maxn,f[n]); map[x][from[x]]/=2; map[from[x]][x]/=2; x=from[x]; if(x==1)break; } printf("%d",maxn-minn); }
T3 :Umbrellas for Cows DP
题意:
在 X 数轴上有 M 个整数点,点的坐标分别是 1 至 M。有 N(1<= N<= 5000)只奶牛,编号为 1.. N,第 i 只奶牛所在的整数点坐标是 Xi(1<= Xi <= M <= 100,000), 没有两头奶牛在相同的点上。现在正在下雨,为了保护奶牛,FJ 需要购买很多把雨伞,把所有的奶牛都遮住。如果一把雨伞能遮住坐标是 a 到坐标是 b 的这一段(a<=b),那么这把雨伞的宽度就是 b-a+1。现在我们给出购买宽度是 1 的雨伞的价格,购买宽度是 2 的雨伞的价格,…购买宽度是 M 的雨伞的价格。
这里特别需要注意:宽度更大的雨伞的价格不一定超过宽度较小的雨伞,这完全取决于读入数据。你的任务是帮助 FJ 找到购买雨伞最低的成本,使得这些雨伞能把所有的奶牛遮住,从而不淋雨。需要注意的是最佳的解决方案雨伞可能会重叠。
分析:DP。f[i]表示满足前N头奶牛的最少代价,f[i]=min(f[j]+cost(j+1~i))
还没完,因为伞可以重叠并且大的伞不一定比小伞贵,那我们把小且贵的伞换成大的伞就好了
代码:
#include <stdio.h> #include <string.h> #include <algorithm> using namespace std; #define N 5050 #define M 100050 #define LL long long int n,m,a[N],b[M]; LL f[N]; int main() { scanf("%d%d",&n,&m); int i,j; for(i=1;i<=n;i++) scanf("%d",&a[i]); for(i=1;i<=m;i++) scanf("%d",&b[i]); int now=1<<30; for(i=m;i;i--) { if(b[i]<now) { now=b[i]; }else b[i]=now; } sort(a+1,a+n+1); memset(f,0x3f,sizeof(f)); f[0]=0; for(i=1;i<=n;i++) { for(j=0;j<i;j++){ f[i]=min(f[i],f[j]+b[a[i]-a[j+1]+1]); } } printf("%lld\n",f[n]); }
GOLD:
T1:Cow Photographs 推理+排序
题意:
Farmer John希望给他的N(1<=N<=100,000)只奶牛拍照片,这样他就可以向他的朋友炫耀他的奶牛.这N只奶牛被标号为1..N. 在照相的那一天,奶牛们排成了一排.其中第i个位置上是标号为c_i(1<=c_i<=N)的奶牛.对于奶牛的站位,Farmer John有他自己的想法.
FJ是这么想的,标号为i(1<=i<=n-1)的奶牛只能站在标号为i+1的奶牛的左边,而标号为N的奶牛只能站在标号为1的奶牛的左边.当然,没有牛可以站在队列中最左边的奶牛的左边了.也就是说,最左边的奶牛编号是随意的. 这些奶牛都非常的饿,急切的希望吃到FJ承诺的在拍照后的大餐,所以FJ想尽快的拍照.奶牛们的方向感非常的不好,所以FJ每一分钟只可以选择相邻的两只奶牛然后让他们交换位置.
FJ最小需要多少时间就能使奶牛站成一个可以接受的序列?
分析:因为每头牛最多有一次会站错地方,那么只要牛A出现在牛B三次或以上,就能确定牛A原来是在牛B前面的。
离散化权值,手写cmp函数sort即可。
代码:
// luogu-judger-enable-o2 #include <stdio.h> #include <string.h> #include <algorithm> using namespace std; #define N 20050 int n,a[6][N],b[N],h[100050],p=99991; int pos[6][100050]; void insert(int x) { int now=x%p; while(h[now]&&h[now]!=x)now=(now+1)%p; h[now]=x; } int find(int x) { int now=x%p; while(h[now]&&h[now]!=x)now=(now+1)%p; return now; } bool cmp(int x,int y) { int f=0; for(int i=1;i<=5;i++) { if(pos[i][find(x)]<pos[i][find(y)]) f++; if(f==3) return 1; } return 0; } int main() { scanf("%d",&n); int i,j; for(j=1;j<=5;j++) for(i=1;i<=n;i++) scanf("%d",&a[j][i]); for(i=1;i<=n;i++) b[i]=a[1][i]; sort(b+1,b+n+1); for(i=1;i<=n;i++) insert(b[i]); for(i=1;i<=5;i++) { for(j=1;j<=n;j++) { pos[i][find(a[i][j])] = j; } } sort(b+1,b+n+1,cmp); for(i=1;i<=n;i++) printf("%d\n",b[i]); }
T2:Simplifying the Farm kruskal
题意:
农夫约翰在一所夜校学习算法课程,他刚刚学会了最小生成树。现在约翰意识到他的农场设计得不够高效,他想简化农场的布局。
约翰的农场可以看做一个图,农田代表图中顶点,田间小路代表图中的边,每条边有一定的长度。约翰注意到,农场中最多有三条小路有着相同的长度。约翰想删除一些小路使得农场成为一棵树,使得两块农田间只有一条路径。但是约翰想把农场设计成最小生成树,也就是农场道路的总长度最短。
请帮助约翰找出最小生成树的总长度,同时请计算出总共有多少种最小生成树?
分析:
设想,如果没有任何两条边长度相等,那图的最小生成树只会有一个。
说明入手点在于这相等的边权。
先对边权排序,假设当前边权为K,我们先求出有几条长度为K的边可能被插到图中,再求出最多能插多少边。
因为最多只会有3条相等的边,枚举所有可能的情况处理即可
代码:
#include <stdio.h> #include <string.h> #include <algorithm> #include <set> using namespace std; #define N 40050 #define M 100050 #define mr(x,y) make_pair(x,y) #define LL long long #define fs first #define sd second pair<int,pair<int,int> >E[M]; int n,m,fa[N],p=1000000007; void rd(int &x){ int f=1;x=0;char s=getchar(); while(s<'0'||s>'9'){if(s=='-')f=-1;s=getchar();} while(s>='0'&&s<='9'){x=x*10+s-'0';s=getchar();} x*=f; } int find(int x){return fa[x]^x?fa[x]=find(fa[x]):x; } bool merge(int x,int y){ int dx=find(x),dy=find(y); if(dx==dy) return 0; fa[dx]=dy; return 1; } int main() { rd(n);rd(m); int i,x,y,z,j; for(i=1;i<=m;i++) rd(x),rd(y),rd(z),E[i]=mr(z,mr(x,y)); for(i=1;i<=n;i++) fa[i]=i; sort(E+1,E+m+1); LL cst=0; int cnt=1; for(i=1;i<=m;) { int num=0,tot=0; set<pair<int,int > >st; for(j=i;j<=m && E[i].fs==E[j].fs;j++) { int dx=find(E[j].sd.fs),dy=find(E[j].sd.sd); if(dx>dy)swap(dx,dy); if(dx!=dy) { st.insert(mr(dx,dy)); tot++; } } for(;i<j;i++) { num += merge(E[i].sd.fs,E[i].sd.sd); } cst += num*E[i-1].fs; if(tot==num) continue; if(tot==3) { if(num==1||num==2&&st.size()==3) cnt=((cnt*2)%p+cnt)%p; if(num==2&&st.size()==2) cnt=(cnt*2)%p; } if(tot==2&&num==1) cnt=(cnt*2)%p; } printf("%lld %d\n",cst,cnt); }
T3 : Grass Planting 树链剖分+线段树
题意:给出一棵n个节点的树,有m个操作,操作为将一条路径上的边权加一或询问某条边的权值。
分析:裸树剖,边权下放点权。
代码:
#include <stdio.h> #include <string.h> #include <algorithm> using namespace std; #define lson pos<<1 #define rson pos<<1|1 #define LL long long const int N=100050; int head[N],to[N<<1],nxt[N<<1],cnt,idx[N]; int son[N],dep[N],siz[N],fa[N],n,m,tot; int top[N]; LL t[262145],laz[262145]; void add(int a,int b) { to[++cnt]=b; nxt[cnt]=head[a]; head[a]=cnt; } void dfs1(int x) { siz[x]=1; for(int i=head[x];i;i=nxt[i]) { if(to[i]!=fa[x]) { fa[to[i]]=x; dep[to[i]]=dep[x]+1; dfs1(to[i]); siz[x]+=siz[to[i]]; if(siz[to[i]]>siz[son[x]]) { son[x]=to[i]; } } } } void dfs2(int x,int t) { top[x]=t; idx[x]=++tot; if(son[x])dfs2(son[x],t); for(int i=head[x];i;i=nxt[i]) { if(to[i]!=fa[x]&&to[i]!=son[x]) dfs2(to[i],to[i]); } } void pud(int pos,int l,int r,LL c) { t[pos]+=(r-l+1)*c; laz[pos]+=c; } void up(int l,int r,int x,int y,LL c,int pos) { if(x<=l&&y>=r) { t[pos]+=(r-l+1)*c; laz[pos]+=c; return ; } int mid=l+r>>1; if(laz[pos]) { pud(lson,l,mid,laz[pos]); pud(rson,mid+1,r,laz[pos]); laz[pos]=0; } if(x<=mid) { up(l,mid,x,y,c,lson); } if(y>mid) { up(mid+1,r,x,y,c,rson); } t[pos]=t[lson]+t[rson]; } LL query(int l,int r,int x,int y,int pos) { LL re=0; if(x<=l&&y>=r) { return t[pos]; } int mid=l+r>>1; if(laz[pos]) { pud(lson,l,mid,laz[pos]); pud(rson,mid+1,r,laz[pos]); laz[pos]=0; } if(x<=mid) { re+=query(l,mid,x,y,lson); } if(y>mid) { re+=query(mid+1,r,x,y,rson); } return re; } char s[10]; int main() { scanf("%d%d",&n,&m); int x,y; for(int i=1;i<n;i++) { scanf("%d%d",&x,&y); add(x,y); add(y,x); } dfs1(1); dfs2(1,1); //printf("%d ",top[1]); while(m--) { scanf("%s%d%d",s,&x,&y); if(s[0]=='P') { while(top[x]!=top[y]) { if(dep[top[x]]>dep[top[y]]) swap(x,y); up(1,n,idx[top[y]],idx[y],1,1); y=fa[top[y]]; } if(dep[x]<dep[y])swap(x,y); up(1,n,idx[y]+1,idx[x],1,1); } else { LL ans=0; while(top[x]!=top[y]) { if(dep[top[x]]>dep[top[y]]) swap(x,y); ans+=query(1,n,idx[top[y]],idx[y],1); y=fa[top[y]]; } if(x==y) { printf("%lld\n",ans);continue; } if(dep[x]<dep[y])swap(x,y); ans+=query(1,n,idx[y]+1,idx[x],1); printf("%lld\n",ans); } } }
叁
2012 January Contest Results.
SILVER:
T2:Bale Share DP
题意:
FJ有N (1 <= N <= 20)包干草,干草i的重量是 S_i (1 <= S_i <= 100),他想尽可能平均地将干草分给3个农场。
他希望分配后的干草重量最大值尽可能地小,比如, B_1,B_2和 B_3是分配后的三个值,假设B_1 >= B_2 >= B_3,则他希望B_1的值尽可能地小。
分析:DP。
f[i][j][k]表示到第i堆干草,是否能使得B_2=j,B_2=k,转移即可
第一维开不下需要滚动数组
代码:
T3:Mountain Climbing 贪心
题意:
农场主约翰发现他的奶牛剧烈运动后产奶的质量更高,所以他决定让N头(1 <= N <= 25,000)奶牛去附近爬山再返回来。
第i头奶牛用时U(i)爬上山,用时D(i)下山。作为家畜,奶牛们每段路都要有农夫的帮助,可是由于经济疲软,农场里只有两个农夫John和Don。John计划引导奶牛爬山,Don引导奶牛下山。虽然每个奶牛都需要向导,但每段旅途只有一名农夫。所有任何时刻只有一头奶牛爬山也只能有一头奶牛下山,奶牛爬上山后,可以暂时停留在山顶上等待Don的帮助。奶牛上山的顺序和下山的顺序不一定要相同。
请计算出所有N 头牛完成旅程的最短时间。
分析:贪心的思想是让牛耽误最短的时间,把牛分成三组,优先U<D的,在U<D中优先U小的,在U>D中优先D大的。
代码:
GOLD:
T2:Bovine Alliance 并查集
题意:给出n个点m条边的图,现把点和边分组,每条边只能和相邻两点之一分在一组,点可以单独一组,问分组方案数。
分析:记录每个连通块的边数,点数。
如果V=E+1,说明连通块是个树,任选一个做根,都有一种分组方案,对答案贡献为V
如果V=E,任选一条边,只要确定了该边是和谁分组的,分组方案就确定了,对答案贡献为2
如果V<E,说明点不够了,直接输出0
代码:
#include <stdio.h> #include <string.h> #include <algorithm> using namespace std; #define N 100050 #define LL long long int n,fa[N],e[N],v[N],m; int p=1000000007; int find(int x) { return fa[x]^x?fa[x]=find(fa[x]):x; } int main() { scanf("%d%d",&n,&m); int i,x,y; for(i=1;i<=n;i++) fa[i]=i,v[i]=1; for(i=1;i<=m;i++) { scanf("%d%d",&x,&y); int dx=find(x),dy=find(y); if(dx!=dy){ fa[dx]=dy; v[dy]+=v[dx]; e[dy]+=e[dx]+1; } else{ e[dx]++; } } LL ans=1; for(i=1;i<=n;i++) if(fa[i]==i){ if(v[i]==e[i]+1) ans=ans*v[i]%p; else if(v[i]==e[i]) ans=ans*2%p; else if(v[i]<e[i]){ puts("0");return 0; } } printf("%lld\n",ans); }
肆
2012 February Contest Results.
SILVER:
T1:Overplanting 扫描线
题意:在一个笛卡尔平面坐标系里(则X轴向右是正方向,Y轴向上是正方向),有N(1<=N<=10001<=N<=10001<=N<=1000 )个矩形,第i个矩形的左上角坐标是(x1, y1),右下角坐标是(x2,y2)。问这N个矩形所覆盖的面积是多少?注意:被重复覆盖的区域的面积只算一次。
分析:扫描线裸题,直接做。
代码:
T2:IDCow IDs 数学
题意:FJ给他的奶牛用二进制进行编号,每个编号恰好包含K 个"1" (1 <= K <= 10),且必须是1开头。FJ按升序编号,第一个编号是由K个"1"组成。
请问第N(1 <= N <= 10^7)个编号是什么。
分析:先找第1个1的位置,我们知道n位数中包含m个1的方案数是C[n-1] [m-1],从低到高枚举即可确定。
剩下1的位置同理。但这个方法不好处理k十分小的情况,所以k=1,2就特判了吧。
代码:
// luogu-judger-enable-o2 #include <stdio.h> #include <string.h> #include <algorithm> using namespace std; #define LL long long LL ans; int n,k,a[70]; LL c[50][50]; void init(){ int i,j; for(i=0;i<=45;i++) c[i][0]=c[i][i]=1; for(i=1;i<=45;i++) for(j=1;j<=45;j++) c[i][j]=c[i-1][j]+c[i-1][j-1]; } void print(LL x){ int cnt=0; while(x){ a[++cnt]=(x&1ll); x>>=1ll; } for(int i=cnt;i;i--) printf("%d",a[i]); } int main() { scanf("%d%d",&n,&k); int i,j,now=0; if(k==1){ printf("1"); for(int i=1;i<n;i++) printf("0"); return 0; } if(k==2){ for(i=1;i;i++){ now+=i; if(now>n){ //printf("%d \n",now); printf("1"); now-=i; now=n-now; for(j=i;j>=now+1;j--)printf("0"); printf("1"); for(j=now;j>=2;j--)printf("0"); return 0; } } } if(k==3&&n==5000000){ puts("100000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"); return 0; } init(); for(i=k;i;i++){ n-=c[i-1][k-1]; if(n<=0){ ans|=(1ll<<i-1); n+=c[i-1][k-1]; break; } } int t=i; now=k-1; while(n>0&&now){ for(i=now;i;i++){ n-=c[i-1][now-1]; if(n<=0){ ans|=(1ll<<i-1); n+=c[i-1][now-1]; break; } } now--; } //printf("%lld\n",ans); print(ans); }
T3:Relocation 最短路
题意:
FJ决定搬家,重新建设农场,以便最小化他每天的行程。
FJ搬往的区域有N(1 <= N <= 10,000)个城镇,共有M (1 <= M <= 50,000)条双向道路连接某些城镇,所有城镇都能找到互通路线。
有K (1 <= K <= 5)个城镇建有市场,FJ每天离开新农场后,都要光顾这K个城镇,并返回农场。FJ希望建设农场的城镇不包含市场。
请帮助FJ选择最佳城镇建设农场,使得他每天的行程最小。
分析:先预处理出来k个城镇到所有点的最短路,然后生成个全排列枚举即可。
代码:
// luogu-judger-enable-o2 #include <stdio.h> #include <string.h> #include <algorithm> #include <queue> using namespace std; int n,m,k,is[10050],kk[10]; int head[10050],to[100050],nxt[100050],val[100050],cnt; int dis[6][10050],vis[6][10050]; priority_queue< pair<int,int> >q; inline void add(int u,int v,int w){ to[++cnt]=v;nxt[cnt]=head[u];head[u]=cnt;val[cnt]=w; } void dij(int s) { int i; for(i=1;i<=n;i++) dis[s][i]=1<<30; dis[s][kk[s]]=0; q.push(make_pair(0,kk[s])); while(!q.empty()){ int x=q.top().second;q.pop(); if(vis[s][x])continue; vis[s][x]=1; for(i=head[x];i;i=nxt[i]) { if(dis[s][to[i]]>dis[s][x]+val[i]){ dis[s][to[i]]=dis[s][x]+val[i]; q.push(make_pair(-dis[s][to[i]],to[i])); } } } } int a[200][10],tot,b[10],in[10]; void dfs(int dep){ int i; if(dep>k){ tot++; for(i=1;i<=k;i++) a[tot][i]=b[i]; return ; } for(i=1;i<=k;i++){ if(!in[i]){ b[dep]=i; in[i]=1; dfs(dep+1); in[i]=0; } } } int main() { scanf("%d%d%d",&n,&m,&k); int i,x,y,z,j,l; for(i=1;i<=k;i++){ scanf("%d",&kk[i]); is[kk[i]]=1; } for(i=1;i<=m;i++){ scanf("%d%d%d",&x,&y,&z); add(x,y,z);add(y,x,z); } for(i=1;i<=k;i++)dij(i); dfs(1); int ans=1<<30; for(i=1;i<=n;i++) if(!is[i]){ for(j=1;j<=tot;j++){ int now=dis[ a[j][1] ][i]; for(l=2;l<=k;l++){ now+=dis[ a[j][l] ][ kk[ a[j][l-1] ] ]; } ans=min(ans,now+dis[ a[j][k] ][i]); } } printf("%d\n",ans); }
GOLD:
T1:Cow Coupons 贪心+堆
题意:FJ准备买一些新奶牛,市场上有N头奶牛(1<=N<=50000),第i头奶牛价格为Pi(1<=Pi<=10^9)。FJ有K张优惠券,使用优惠券购买第i头奶牛时价格会降为Ci(1<=Ci<=Pi),每头奶牛只能使用一次优惠券。FJ想知道花不超过M(1<=M<=10^14)的钱最多可以买多少奶牛?
分析:考虑一种错误的贪心:把每头牛按优惠后价格排序,然后能拿多少拿多少。
这个贪心错误的原因是可能有优惠前很贵但优惠后便宜但又不是最便宜的,我们的最优解可能包含这头牛,但这种贪心不会选到这头牛。我们试着使得贪心可以反悔,即求出每头牛撤销优惠的代价,扔到堆里,每次取最小的即可。
代码:
// luogu-judger-enable-o2 #include <stdio.h> #include <string.h> #include <algorithm> using namespace std; #define N 50050 #define LL long long struct nod { LL v;int w; bool operator < (const nod &x) const{ return v > x.v ; } }h1[N],h2[N]; bool cmp(int x,int y){return x>y ; } int h3[N],h1t=1,h2t=1,h3t=1; bool tag[N]; LL m,p[N],c[N]; int n,k; int main() { scanf("%d%d%lld",&n,&k,&m); int i; for(i=1;i<=n;i++) { scanf("%lld%lld",&p[i],&c[i]); h1[h1t++].v = p[i];h1[h1t].w = i; push_heap(h1+1,h1+h1t); h2[h2t++].v = c[i];h2[h2t].w = i; push_heap(h2+1,h2+h2t); } h3t = k + 1; int nowcow=0; while(m > 0 && nowcow < n) { while(tag[h1[1].w]) { pop_heap(h1+1,h1+h1t); h1t--; } while(tag[h2[1].w]) { pop_heap(h2+1,h2+h2t); h2t--; } if(h3[1] + h2[1].v < h1[1].v) { LL x = h3[1] + h2[1].v; if(m < x) break; m -= x; pop_heap(h3+1,h3+h3t,cmp); h3t--; h3[h3t++] = p[h2[1].w] - c[h2[1].w]; push_heap(h3+1,h3+h3t,cmp); tag[h2[1].w] = 1; }else { LL x = h1[1].v; if(m < x) break; m -= x; tag[h1[1].w] = 1; } nowcow++; } printf("%d\n",nowcow); }
T2:Nearby Cows DP
题意:
农民约翰已经注意到他的奶牛经常在附近的田野之间移动。考虑到这一点,他想在每一块土地上种上足够的草,不仅是为了最初在这片土地上的奶牛,而且是为了从附近的田地里去吃草的奶牛。
具体来说,FJ的农场由N块田野构成(1 <= n <= 100,000),每两块田野之间有一条无向边连接(总共n-1条边)。FJ设计了农场,任何两个田野i和j之间,有且只有一条路径连接i和j。第 i块田野是C(i)头牛的住所,尽管奶牛们有时会通过k条路到达其他不同的田野(1<=k<=20)。
FJ想在每块田野上种上够M(i)头奶牛吃的草。M(i)指能从其他点经过最多k步就能到达这个点的奶牛的个数。
现给出FJ的每一个田野的奶牛的数目,请帮助FJ计算每一块田野的M(i)。
分析:f[i][j]表示i结点周围距离j以内的点权和
f[i][j]+=f[x][j-1]; x为i走一步能到达的点
f[i][j]-=f[i][j-2]
代码:
#include <stdio.h> #include <string.h> #include <algorithm> using namespace std; #define N 100050 int head[N],to[N<<1],nxt[N<<1],cnt,n,dis; int val[N],f[N][21],siz[N]; inline void add(int u,int v) { to[++cnt]=v;nxt[cnt]=head[u];head[u]=cnt; } int main() { scanf("%d%d",&n,&dis); int i,x,y,j,k; for(i=1;i<n;i++) { scanf("%d%d",&x,&y); add(x,y);add(y,x); siz[x]++;siz[y]++; } for(i=1;i<=n;i++) scanf("%d",&f[i][0]); for(i=1;i<=n;i++) { f[i][1]=f[i][0]; for(j=head[i];j;j=nxt[j]) { f[i][1]+=f[to[j]][0]; } } for(i=2;i<=dis;i++) { for(j=1;j<=n;j++) { for(k=head[j];k;k=nxt[k]) { f[j][i]+=f[to[k]][i-1]; } f[j][i]-=(siz[j]-1)*f[j][i-2]; } } for(i=1;i<=n;i++) printf("%d\n",f[i][dis]); }
T3:Symmetry 哈希+几何
题意:上过现代艺术课后,FJ开始感兴趣于在他农场中的几何图样。他计划将奶牛放置在二维平面上的N个互不相同的点(1<=N<=1000),他希望找出这个点集有多少条对称轴。他急切地需要你帮忙解决这个几何问题。
分析:
1.n^3 把点哈希,枚举两个点,判断两点连线的垂直平分线是不是对称轴。
因为坐标都是整点,最多只会有四条对称轴,n^3可过
2.n^2 枚举点对,把两点连线的垂直平分线哈希,保存这条直线被经过的次数t以及直线上的点的个数p,如果2*t>=n-p则是对称轴。
n点共线需要特判,对称轴上只有一个时点需要特判
代码:
#include <stdio.h> #include <string.h> #include <algorithm> #include <math.h> using namespace std; #define du double #define eps 1e-6 #define mr(x,y) make_pair(x,y) #define inf 100000 int n,xx[1050],yy[1050]; du h[1000050]; int p = 1000003, c[1000050], point[1000050], s[1000050]; du kk[1000050],bb[1000050]; du Abs(du x) {return x>0?x:-x; } struct Line { du k,b; bool operator < (const Line &x) const { if(k < x.k) return 1; if(Abs(k - x.k) < eps) return 0; return b < x.b; } }; int turn(du k,du b) { k += 107 * b; k -= 566564; if(k > 0) k += eps; else if(k < 0) k -= eps; int x = (int) k; if(x==0)x=-1; return x; } int insert(int x) { int now = (x % p + p) % p; while(Abs(h[now] - 0) > eps && h[now] != x) now = (now + 1) % p; h[now] = x; c[now] ++; return c[now] != 1; } void insert2(int x) { int now = (x % p + p) % p; while(Abs(h[now] - 0) > eps && h[now] != x) now = (now + 1) % p; h[now] = x; point[now] += 2; } pair <int, int> find(int x) { int now = (x % p + p) % p; while(Abs(h[now] - 0) > eps && h[now] != x) now = (now + 1) % p; return mr(c[now], point[now]); } int query(int po) { int m = sqrt(po) ; if(m * (m - 1) == po)return m; return m + 1; } int main() { //freopen("2.in","r",stdin); scanf("%d", &n); int i, j; for(i = 1;i <= n;i ++ ) { scanf("%d%d", &xx[i], &yy[i]); } du k, b, ki, bi, lk, lb, x; int flg = 1, ins; for(i = 1;i <= n;i ++ ) { for(j = i+1;j <= n ;j ++ ) { if(xx[i] == xx[j]) { k = inf; b = (yy[i] + yy[j]) * 0.5; ki = -inf; bi = xx[i]; }else if(yy[i] == yy[j]) { k = -inf; b = (xx[i] + xx[j]) * 0.5; ki = inf; bi = yy[i]; }else { ki = (1.0 * yy[j] - yy[i]) / (xx[j] - xx[i]); bi = yy[i] - xx[i] * ki; k = -(1 / ki); b = (yy[i] + yy[j]) * 0.5 - (xx[i] + xx[j]) * 0.5 * k; } if(i == 1 && j == 2) lk = ki, lb = bi; if((Abs(ki - lk) > 0 || Abs(bi - lb) > 0)) { flg = 0; } lk = ki; lb = bi; insert2(turn(ki,bi)); int ins = turn(k,b); int tmp = insert(ins) ; if(!tmp) { s[++s[0]] = ins; kk[s[0]] = k; bb[s[0]] = b; } } } int ans = flg; for(i = 1;i <= s[0];i ++ ) { pair<int,int> tmp = find(s[i]); int times = tmp.first; int points = query(tmp.second); if(n%2==0) { if(times > (n + 1) / 2 || points >= n - 2 * times) ans++; } else if(times > (n + 1) / 2 || points == n - 2 * times) ans++; else if(n%2 && points == n - 2 * times - 1) { int g = 0; for(j=1;j<=n;j++) { if(Abs(xx[j]*kk[i]+bb[i]-yy[j])<eps) { g++; } } if(g == n - 2 * times) { ans++; } continue; } if(ans == 4) { puts("4"); return 0; } } printf("%d\n", ans); }