盾哥說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;
}
day2:
分裂遊戲
題意,給定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; }