【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;
}





發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章