【hnoi2007】


      盾哥说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;
}





   

你可能感兴趣的:(【hnoi2007】)