盾哥说hnoi年份靠前的题目价值不大,所以考虑07年选做一些题后转战poi吧。
hnoi2007简直是奇葩的一年,day1明显难于day2(day1就算算上的第四题(那个时候还没有cdq的论文),也只有两道可做题,而day2有三道可做题)
day1
海盗分宝:
。。。。。。
求神牛解释题意。。。。。。
bzoj 0 Submit 0 AC
最小矩形覆盖:
题意: 用面积最小的矩形覆盖给定的点,点数:50000
很显然的是,必定有两点同时在一条边上时,面积才会最小,可以想象,如果每条边只有1个点在上面,我们可以把这个矩形缩小,而矩形可以通过旋转保证覆盖所有点,
直到有两个点在一条边上从而限制了矩形的旋转。
有了这个猜想,我们可以写出一个利用4条直线进行卡壳的方法,先做遍凸包, 在凸包上卡出最左点,最右点,最高点,最低点,动态维护,更新面积。
//用向量表示直线,第一个点精度被卡>.<太凄惨了=。=!55~~~~~~~ 反正是写的无比丑陋。
# include <cstdlib> # include <cstdio> # include <cmath> # include <cstring> using namespace std; const int N = 60000; const double eps = 1e-9; struct point {double x, y, z;}pans[5], zd[5], vec[5], peak[5], line[N],d[N], edge[N], pt[N]; int nex[N], que[N], top, n; int g[N]; double ans=1e100, Cos[N], Sin[N]; bool bezero(double x) {return x<eps&&x>-eps?true:false;}; double max(double x, double y) {return x>y?x:y;}; int cmp(const void *i, const void *j) { point p = *(point *)i, q = *(point *)j; if (p.y<q.y-eps||(bezero(p.y-q.y)&& p.x<q.x-eps)) return -1; else return 1; } double dot(point u, point v) { return u.x*v.x+u.y*v.y; } double cross2(point u, point v, point w) { double x1 = v.x-u.x,y1 = v.y-u.y, x2=w.x-u.x, y2=w.y-u.y; return x1*y2 - x2*y1; } double sqr(double x) {return x*x;}; void simple(point &u) { double len = sqrt(sqr(u.x)+sqr(u.y)); u.x/=len; u.y/=len; } double dist(point u, point v) { return (sqrt(sqr(u.x-v.x)+sqr(u.y-v.y))); } void prepare() { int i; qsort(d+1, n, sizeof(d[1]), cmp); que[1] = 1; top = 1; for (i = 2; i <= n; i++) { for (;top > 1&&cross2(d[que[top-1]], d[que[top]], d[i])<=0;top--); que[++top] = i; } for (i = n-1; i >= 2; i--) { for (;top > 1&&cross2(d[que[top-1]], d[que[top]], d[i])<=0;top--); que[++top] = i; } for (i = 1; i <= top; i++) pt[top-i+1] = d[que[i]]; } point cross(point u, point v) { point g; g.x= u.y*v.z - u.z*v.y; g.y= u.z*v.x - u.x*v.z; g.z= u.x*v.y - u.y*v.x; if (!bezero(g.z)) g.x/=g.z, g.y/=g.z, g.z=1; return g; } point spin(point p, double Sin, double Cos) { point g; g.x = Cos* p.x+Sin*p.y; g.y = -Sin*p.x+Cos*p.y; g.z = p.z; return g; } void update() { int i; for (i = 1; i <= 4; i++) { zd[i].x=pt[g[i]].x+vec[i].x; zd[i].y=pt[g[i]].y+vec[i].y; zd[i].z=1; line[i]=cross(zd[i], pt[g[i]]); } for (i = 1; i <= 3; i++) peak[i]=cross(line[i], line[i+1]); peak[4] = cross(line[4], line[1]); double length = dist(peak[1], peak[2]), wide = dist(peak[1], peak[4]); if (length*wide < ans+eps) { ans = length*wide; for (i = 1; i <= 4; i++) pans[i] = peak[i]; } } void work() { int stop, i; double SIN, COS; for (i = 2, g[1] = 1; i <= top; i++) if (pt[i].y>pt[g[1]].y) g[1] = i; for (i = 2, g[2] = 1; i <= top; i++) if (pt[i].x>pt[g[2]].x) g[2] = i; for (i = 2, g[3] = 1; i <= top; i++) if (pt[i].y<pt[g[3]].y) g[3] = i; for (i = 2, g[4] = 1; i <= top; i++) if (pt[i].x<pt[g[4]].x) g[4] = i; vec[1].x = 1, vec[1].y = 0; vec[2].x = 0; vec[2].y = -1; vec[3].x = -1, vec[3].y = 0; vec[4].x = 0; vec[4].y = 1; for (i = 1; i < top; i++) nex[i] = i+1; nex[top] = 1; for (i = 1; i <= top; i++) edge[i].x = pt[nex[i]].x-pt[i].x, edge[i].y = pt[nex[i]].y-pt[i].y, edge[i].z=1,simple(edge[i]); for (stop = g[1];g[3] != stop;) { for (i = 1; i <= 4; i++) Cos[i] = dot(edge[g[i]], vec[i]); COS = max(max(Cos[1], Cos[2]), max(Cos[3], Cos[4])); SIN = sqrt(1-COS*COS); for (i = 1; i <= 4; i++) vec[i] = spin(vec[i], SIN, COS); for (i = 1; i <= 4; i++) if (bezero(Cos[i]-COS)) g[i] = nex[g[i]]; update(); } } int main() { int i; freopen("input.txt", "r", stdin); freopen("output.txt", "w", stdout); scanf("%d", &n); for (i = 1; i <= n; i++) scanf("%lf%lf", &d[i].x, &d[i].y), d[i].z=1; prepare(); work(); printf("%.5lf\n", ans+eps); int bj; for (i = 2, bj = 1; i <= 4; i++) if (pans[i].y<pans[bj].y||(bezero(pans[i].y-pans[bj].y) && pans[i].x <pans[bj].x)) bj = i; for (i = bj; i >= 1; i--) printf("%.5lf %.5lf\n", pans[i].x+eps, pans[i].y+eps); for (i = 4; i >= bj+1; i--) printf("%.5lf %.5lf\n", pans[i].x+eps, pans[i].y+eps); return 0; }
胜负一子:
问五子棋残局的必胜下法。
bzoj 1 submit 0 AC。。。。。。
只知道五子棋可以用随机做估价函数,但是问必胜下法。。。。。。完全无思路,搜索什么的肯定过不了。。。。。。
而且和海盗分宝一样,网上0题解,
又一道不可做神题。
神奇游乐园:
不用说了,一个裸的插头dp模型,写就行了。
(那个时候cdq的论文都没有出来,orz那时A掉此题的人)
# include <cstdlib> # include <cstdio> # include <cmath> # include <cstring> using namespace std; const int oo=1073741819, HASH = 3000; bool step[HASH]; int head[2], tail[2], f[2][HASH], que[2][HASH]; int bct[30], qe[30], hash[30000],top,p; int ans=-oo, n,m, mp[200][200]; inline int max(int x, int y){return x>y?x:y;}; inline int ask(int state) { return hash[state]?hash[state]: hash[state]=++top; } inline int get(int state, int p) { return (state>>((p-1)<<1))&3; } inline void cor(int &state, int p, int alt) { int pri = get(state, p); state ^= pri <<((p-1)<<1); state |= alt <<((p-1)<<1); } void update(int aim, int state, int mpc) { int sha = ask(aim), shs = ask(state); //f[p][sha] = max(f[p][sha], f[!p][shs]+mpc); if (f[!p][shs]+mpc> f[p][sha]) f[p][sha] = f[!p][shs]+mpc; if (!step[sha]) step[sha]=true, que[p][++tail[p]] = aim; } void expand(int state, int line, int list) { int z, i, aim, pX=list+1, pY=list+2, X=get(state, pX), Y=get(state, pY), mpc=mp[line][list+1]; if (list==m) { if (!X) update(state<<2, state, 0); } else if ((!X)&&(!Y)) { update(state, state, 0); aim= state; cor(aim, pX, 1); cor(aim, pY, 2); update(aim, state, mpc); } else if ((!X)||(!Y)) { update(state, state, mp[line][list+1]); aim= state; cor(aim, pX, Y); cor(aim, pY, X); update(aim, state, mpc); } else if (X==Y) { aim =state; memset(bct,0,sizeof(bct)); memset(qe,0,sizeof(qe)); for (i = 1; i <= m+1; i++) { z=get(state, i); if (z==1) qe[++qe[0]] = i; else if (z==2) bct[i]=qe[qe[0]], bct[qe[qe[0]--]]= i; } cor(aim, pX, 0); cor(aim, pY, 0); if (X==1) cor(aim, bct[pY], 1), update(aim, state, mpc); if (X==2) cor(aim, bct[pX], 2), update(aim, state, mpc); } else if (X==2&&Y==1) { aim=state; cor(aim, pX, 0); cor(aim, pY, 0); update(aim, state, mpc); } else if (X==1&&Y==2) { aim = state; cor(aim, pX, 0); cor(aim, pY, 0); if (!aim) ans = max(ans, f[!p][ask(state)]+mpc); } } int main() { int i, j; freopen("input.txt", "r", stdin); freopen("output.txt", "w", stdout); scanf("%d%d", &n, &m); for (i = 1; i <= n; i++) for (j = 1; j <= m; j++) scanf("%d", &mp[i][j]); p=1; head[p]=tail[p]=1; que[p][1]=0; f[p][ask(0)]=0; for (i = 1; i <= n; i++) for (j = 0; j <= m; j++) { p^=1; memset(step, false, sizeof(step)); head[p]=1; tail[p]=0; memset(f[p], 130, sizeof(f[p])); for (;head[!p]<=tail[!p]; head[!p]++) expand(que[!p][head[!p]], i, j); } printf("%d\n", ans); //printf("%d\n", top); return 0; }
分裂游戏
题意,给定n个瓶子(20个而已),每个瓶子中有若干个豆子,两人博弈,每个人进行一次操作:选出(i,j,k) i < j <=k, 在i中取出一个豆子(i中必须有),在j,k中各放入一个豆子。 初始时,豆子不超过10000个,最后不能操作的人输。20组询问。
红果果的博弈题,发现裸搜状态量太大=。=! 只能考虑分成很多个独立的小游戏。可以发现:f[i]表示在i个瓶子中的一个豆子的sg值,每一个豆子是独立的。 把整个游戏分成一个个豆子的小游戏,计算每个位置上的豆子的sg值,最坏O(n^3)的预处理,然后每组询问都可以用O(n^3)的复杂度回答,复杂度已经绰绰有余了。
# include <cstdlib> # include <cmath> # include <cstring> # include <cstdio> using namespace std; const int N = 30; int sp[20000], sg[N], se[N]; int test, ans, ansi, ansj, ansk, way; int n; bool flag; void prepare() { int i, j, k; sg[1] = 0; for (i = 2; i <= 25; i++) { for (j = 1; j < i; j++) for (k = 1; k <= j; k++) sp[sg[j]^sg[k]] = i; for (j = 0; j <= 10000; j++) if (sp[j]!= i) break; sg[i] = j; } } int main() { int i,j,k; freopen("input.txt","r",stdin); freopen("output.txt","w",stdout); prepare(); scanf("%d", &test); while (test--) { scanf("%d", &n); for (i = 1; i <= n; i++) scanf("%d", &se[i]); ans = 0; way = 0; flag= 0; for (i = n; i >= 1; i--) if (se[i]&1) ans ^= sg[n-i+1]; //if (ans > 0) printf("win\n"); else printf("lose\n"); for (i = 1; i <= n; i++) for (j = i+1; j <= n; j++) for (k = j; k <= n; k++) if ((ans^sg[n-i+1]^sg[n-j+1]^sg[n-k+1])==0) { way++; if (!flag) flag=1,ansi=i,ansj=j,ansk=k; } if (flag) printf("%d %d %d\n", ansi-1, ansj-1, ansk-1); else printf("-1 -1 -1\n"); printf("%d\n", way); } return 0; }
题意:比较麻烦,略。
一道很有意思的题。
一开始是这样想的,把联通的空地都看成一个点, 而逃生门看成一个点,空地集合点 向与其响铃的逃生门点连边,形成一个怪怪的二分图。
问题转化为,X部点有流量,要从Y部点流出去,很容易想到网络流,S连X部点,Y部点连T。
有两个问题必须解决:
1. 门每个事件点只能够走出一个人:
解决方法: 枚举时间点,随着时间点的增加,逐渐增加Y部点到T的流量上界。
2.人走到门需要时间:
解决方法:bfs计算每个人到逃生门的距离,然后随着时间点的增加,在枚举的time = 人i 到门j的距离时,把Xi --> Yj 的 流量上限++;
猥琐方法: 昨天肚子不舒服,懒得写了,直接bfs出每个点到最近的门的距离,用哪个最长距离作为答案的下限,数据很难卡,AC之。
也可以用分层图做,这样可能会慢很多。
# include <cstdlib> # include <cmath> # include <cstring> # include <cstdio> using namespace std; const int mx[4]={0,1,0,-1}; const int my[4]={1,0,-1,0}; const int oo=1073741819, V = 410, E=5000; int tab[V], dist[V], que[V], quex[V], quey[V], pre[V], shift[V]; int top=1, linke[E], next[E], point[E], wis[E]; int lim, ans, tot, people, space, door, maxflow, p, s,t,n, m; char c[25][25]; int spt[V], dt[25][25],d[25][25], dpt[V]; int step[25][25]; int max(int x, int y){return x>y?x:y;}; int min(int x, int y){return x<y?x:y;}; void link(int x, int y, int z) { ++top; next[top] = linke[x]; linke[x] = top; point[top] = y; wis[top] =z; ++top; next[top] = linke[y]; linke[y] = top; point[top] = x; wis[top] =0; } bool bfs() { memset(tab, -1, sizeof(tab)); int l=1,r=1,x,y,ke; que[l]=s; tab[s]=1; for (;l<=r;l++) for (ke = linke[x=que[l]]; ke; ke=next[ke]) { if (tab[y=point[ke]]==-1&&wis[ke]) tab[que[++r]=y] = tab[x]+1; if (que[r]==t) return true; } return false; } void improve() { int x, flow=oo; for (x=pre[t];x;x=pre[x]) if (flow>wis[shift[x]]) p=x, flow = wis[shift[x]]; for (x=pre[t];x;x=pre[x]) wis[shift[x]]-= flow, wis[shift[x]^1] += flow; maxflow += flow; } void dfs() { int x,y,ke; bool flag; memcpy(shift, linke, sizeof(shift)); for (x=s;x;) { flag=false; for (ke = shift[x]; ke; ke=next[ke]) if (wis[ke]&&tab[y=point[ke]]==tab[x]+1) { pre[y]=x; shift[x]=ke; flag= true; x=y; if (y==t) improve(), x=p; if (flag) break; } if (!flag) tab[x]=-1, x=pre[x]; } } void floodfill(int x, int y) { int size = 1; spt[++space] = ++tot; int xx,yy,k,l=1,r=1;quex[l]=x; quey[l]=y; step[x][y]=true; for (;l<=r;l++) for (k=0;k<4;k++) { xx=quex[l]+mx[k]; yy=quey[l]+my[k]; if (xx>0&&yy>0&&xx<=n&&yy<=m) if ((!step[xx][yy]) &&(c[xx][yy]=='.')) { step[xx][yy] = true; quex[++r]=xx, quey[r]=yy, size++; } if (c[xx][yy]=='D')link(spt[space], dpt[d[xx][yy]], oo); } link(s, tot, size); people += size; } void bfsdist(int door, int x, int y) { int xx,yy,k,l=1,r=1; dist[1]=0; step[x][y]=door; quex[1]=x;quey[1]=y; for (;l<=r;l++) { for (k=0;k<4;k++) { xx=quex[l]+mx[k]; yy=quey[l]+my[k]; if (xx>0&&yy>0&&xx<=n&&yy<=m) if ((step[xx][yy]!=door) &&(c[xx][yy]=='.')) { step[xx][yy]=door; quex[++r]=xx, quey[r]=yy; dist[r] = dist[l]+1; dt[xx][yy] = min(dt[xx][yy], dist[r]); } } } } int main() { int i, j; freopen("input.txt", "r", stdin); freopen("output.txt", "w", stdout); scanf("%d%d\n", &n, &m); s=n*m+1; t=s+1; memset(dt, 127,sizeof(dt)); for (i = 1; i <= n; i++) { for (j = 1; j <= m; j++) { scanf("%c", &c[i][j]); if (c[i][j] =='D') door++, d[i][j] = door, dpt[door]=++tot,link(tot, t, 0); } scanf("\n"); } for (i = 1; i <= n; i++) for (j = 1; j <= m; j++) if (c[i][j] =='D') bfsdist(d[i][j],i, j); lim = 0; for (i = 1; i <= n; i++) for (j = 1; j <= m; j++) if (c[i][j]=='.'&& dt[i][j] > lim) lim = dt[i][j]; memset(step, 0, sizeof(step)); for (i = 1; i <= n; i++) for (j = 1; j <= m; j++) if ((!step[i][j])&&(c[i][j] == '.')) floodfill(i, j); for (ans = 1; maxflow<people;ans++) { if (ans > 1000) break; for (i = 1; i <= door; i++) wis[i*2]++; while (bfs()) dfs(); } if (ans <=1000) printf("%d", max(ans-1,lim)); else printf("impossible"); return 0; }
题意:容量很大的背包问题, 有利条件是, 每一个物品的重量可以写成a*(2^b)的形式, a<=10,b<=30。
这个是看了网上的题解(写的相当简略)之后才写出来的。
充分利用有利条件, 对于每一个二进制位进行dp,
当然,由于a的存在,该物品可能突破二进制限制,去影响更高位,这没有关系,因为影响的不会太多。
定义f[ i, j] 为考虑了b>=i的物品,还剩(2^i) * j的空间(有余数舍掉),的最大价值。
每次可以把f[i, 1000]以内的值下放到f[ i-1]中,(10*100=1000),也就是考虑i-1时从第i位借1000*(2^i)来用,这已经绰绰有余了。
写的时候要小心点,一个小细节让我WA了N久。
# include <cstdlib> # include <cstdio> # include <cmath> # include <cstring> using namespace std; typedef long long int64; struct jew{int a,b,w;}res[200]; int64 f[35][3000], ans; int lim, n, m; int top, next[200], linke[200], point[200]; void link(int x, int y) { ++top; next[top] = linke[x]; linke[x] = top; point[top] = y; } void origin() { top = 0; memset(f,0,sizeof(f)); memset(linke, 0, sizeof(linke)); memset(next, 0, sizeof(next)); memset(point, 0, sizeof(point)); } inline int min(int x, int y){return x<y?x:y;}; inline int64 max(int64 x, int64 y){return x>y?x:y;}; int main() { int ht, i, j, k, ke; freopen("input.txt", "r", stdin); freopen("output.txt", "w", stdout); for (;;) { scanf("%d%d", &n, &m); if (n==-1) break; ans = 0; origin(); for (i = 1; i <= n; i++) { res[i].a=res[i].b=0; scanf("%d%d", &ht ,&res[i].w); for (;(ht&1)==0;ht>>=1) res[i].b++; res[i].a = ht; link(res[i].b, i); } for (i = 30; i >= 0; i--) { for (j = 0; j <= lim; j++) f[i][j*2+((m>>i)&1)] = f[i+1][j]; for (j = lim*2; j >= 0; j--) f[i][j] = max(f[i][j], f[i][j+1]); lim = min(1000, m>>i); for (ke = linke[i]; ke; ke=next[ke]) { k = point[ke]; for (j = 0; j + res[k].a <= lim; j++) { f[i][j] = max(f[i][j], f[i][j+res[k].a] + res[k].w); ans = max(ans, f[i][j]); } } } printf("%lld\n", ans); } return 0; }