【hnoi2010】


       這些東西發上來沒壞事。

      未完待續=。=......

       chrous:

       題意:略
       傻Xdp,直接設計兩個狀態f[ i , j], g[ i , j]分別表示形成i~j一段,最後放得在最左/最右的方案數,直接轉移即可。

     

# include <cstdlib>
# include <cstdio>
# include <cmath>

using namespace std;

const int mo = 19650827, maxn = 1000+10;
int f[maxn][maxn], g[maxn][maxn];
int i, j, l, r, n, h[maxn];
int main()
{
	freopen("chorus.in", "r", stdin);
	freopen("chorus.out", "w", stdout);
	scanf("%d", &n);
	for (i = 1; i <= n; i++) scanf("%d", &h[i]);
	for (i = 1; i <= n; i++) f[i][i]  = 1;
	for (r = 2; r <= n; r++)
	  for (l = r-1; l >= 1; l--)
	  {
			f[l][r] = ((h[l]<h[l+1])*f[l+1][r] + (h[l]<h[r])*g[l+1][r]) % mo;
			g[l][r] = ((h[r]>h[l])*f[l][r-1] + (h[r]>h[r-1])*g[l][r-1]) % mo; 
	  }
	printf("%d", (f[1][n]+g[1][n])% mo);
	return 0;
}

       planar:

     題意:給定一個圖的哈密頓迴路和所有的邊,問其是否可能是平面圖;點n <= 200, 邊m<=10000,100組數據。

     利用題目給出的哈密頓迴路(我們需要其實只要哈密頓路徑就可以了)

     想象把哈密頓路徑“扯直”,那麼其餘所有的邊都必定分居路徑的兩側。

      如果兩條邊放在同一側會相交,那麼他們必定放在異側,最後判斷是否矛盾。

      2-sat,甚至簡單的並查集都可以解決這個問題。

      唯一的問題是o(m^2)的枚舉邊是否相交太慢了,一直想找到合適的方法優化,結果…….結果…….原來平面圖o(m) = o(n)(被坑了=。=!), 實際上m <= 3*n-6 =.=!,那麼判一判,直接裸就行了!     

  

# include <cstdlib>
# include <cstdio>
# include <cmath>
# include <cstring>

using namespace std;

const int maxn = 10000 + 5;
int ufs[maxn*4], rel[maxn*4];
int g[maxn], x[maxn*4], y[maxn*4], tmp;
int n, m;
int find (int x)
{
	int y;
	if (ufs[x] == x) return x;
	else  { 
		 y = find(ufs[x]); 
		 rel[x] = rel[x] ^ rel[ufs[x]];  
		 return ufs[x] = y; };
}

bool work()
{
	int fi, fj, i, j, k;
	for (i = 1; i<= m; i++) if (g[x[i]] > g[y[i]]) tmp = x[i], x[i] = y[i], y[i] = tmp; 
	for (i = 1; i<= m; i++) ufs[i] = i, rel[i] = 0;
	for (i = 1; i<= m; i++)
	  for (j = 1; j <= m; j++)
	   if (i != j)
	    if (g[x[i]] < g[x[j]] && g[x[j]] < g[y[i]] && g[y[i]] < g[y[j]])
	    {
			fi = find(i); fj = find(j);
			if (fi != fj) ufs[fi] = fj, rel[fi] = 1^rel[i]^rel[j];
			else if ((rel[i]^rel[j]) == 0) 
			  return false;
		}
	return true;
}

int main()
{
	int test, i, k;
	freopen("planar.in", "r", stdin);
	freopen("planar.out", "w", stdout);
	scanf("%d", &test);
	for (int ssss = 1; ssss <= test; ssss++)
	{
		scanf("%d%d", &n, &m);
		memset(g, 0, sizeof(g));
	    memset(x, 0, sizeof(x));
	    memset(y, 0, sizeof(y));
	    memset(ufs, 0, sizeof(ufs));
	    memset(rel, 0, sizeof(rel));
	    int xx, yy, now = 0;
	    for (i = 1; i<= m; i++)
      	{	
	       scanf("%d%d", &xx, &yy);
	       if (xx != yy)x[++now]=xx, y[now]=yy;
        }
        m = now;
	    for (i = 1; i<= n; i++) 
	    { 
	    	scanf("%d", &k);
	    	g[k] = i;
	    };
	
		if (m > 3*n) printf("NO\n");
	 	else if  (work()) printf("YES\n"); else printf("NO\n");
	}
	return 0;
}

fsk: 暫未做=。=!......題目都木有看懂=。=


bus:

題意: 公路上有1~n個站,k個公交車,1~k是他們的起點站,n-k+1~n是重點站。公交車從編號小的站開往編號大的站,一個站有且僅有一個公交車停靠,對於一個公交車,它停靠的任意兩個站之間,編號距離不得超過p; 

K,p <= 10, n <=10^9;


 n相當大,k相當小,不用想就是狀壓矩乘了=。=!

  但是如果直接記錄每輛這距離當前的距離,那麼信息量so 大了;

  注意到公交車是無差別的,所以我們只需要記錄距離當前位置p以內的所有公交車的距離就行了......有點繞。

反正狀態被壓縮到了C(9,5)以內,可以狀壓矩乘了。


# include <cstdlib>
# include <cstdio>
# include <cmath>
# include <cstring>

using namespace std;

const int size = 130, mo = 30031;
bool a[size][20];
int t[size][size], ml[size][size], c[size][size];
int top = 1, n, kn, p;

void dfs(int have, int past)
{
	int i;
	if (have == kn)
	{
		top++;
		for (i = 1; i <= kn; i++) a[top][i] = a[top-1][i];
	}
	else
		for (i = past+1; i <= p; i++) 
		{
			a[top][i] = true;
			dfs(have+1, i);
			a[top][i] = false;
		}
}

void prepare()
{
	int flag ; int i, j, k;
	for (i = 1; i <= top; i++)
	  for (j = 1; j <= top; j++)
	  {
			flag = 1;
			for (k = 1; k <= p; k++)
			  if ((a[i][k]^a[j][k+1])) flag --;
			if (flag == 0)  c[i][j] = ml[i][j] = 1;
	  }
}

void mul(int c[size][size], int a[size][size], int b[size][size])
{
	int i, j, k;
	memset(t, 0, sizeof(t));
	for (i = 1; i <= top; i++)
	  for (j = 1; j <= top; j++)
	    for (k = 1; k <= top; k++)
	      t[i][j] = (t[i][j]+a[i][k]*b[k][j]) % mo;
	for (i = 1; i <= top; i++)
	  for (j = 1; j <= top; j++)
	     c[i][j] = t[i][j];
}

int main()
{
	freopen("bus.in", "r", stdin);
	freopen("bus.out", "w", stdout);
	scanf("%d%d%d", &n, &kn, &p);
	a[top=1][1] = true; dfs(1, 1);
	top--;prepare(); 
	for (n-=kn+1; n >0; n >>=1, mul(ml,ml,ml))
        if (n & 1) mul(c,c,ml);
    //  for (n-=kn+1; n >0; n--)
      //   mul(c,c,ml);
    printf("%d", c[1][1]);
	return 0;
}

stone:

    

題意:給定n堆石子,實現有幾堆的石子被取走了,兩人博弈取石子,一堆石子被取當且僅當它左邊或右邊的被取走。問先後手分別能去多少石子。

 

    看到這道題,似曾相識中=。=,貌似去年一次考試出現了這個題目,那個時候是聽了劉堯學長解釋了好久才明白,最後也寫得暈暈的。

     仔細想了想大致回想起了思路, 統計兩選手石子數目之差。取走的石子將這個序列分成了若干塊,最理想的情況是:

  ↗, ↘↗,….., ↘↗, ↘,這樣的情況直接貪心取就ok了;

但是情況肯定要複雜的多,但是 可以修復。

對於↘↗被破壞(單純的↘或↗也可看做↘↗), 一定出現了↗↘的情況,即a[ i -1]< a[ i] , a[i]> a[i + 1], 對於這種情況,“先手”必取a[i- 1] 和a[i + 1], 後手必取a[I], 這樣,三個數可以合併成a[i+1]+a[i-1]-a[i]; 而對於左右兩邊的 ↗和↘, 分別有 ↘,↗的情況與之破壞,這樣的情況“先手”取必虧,大家都不願意先取它,所以一定會留到最後,那麼,誰回先取到它們可以推算出來,這樣,它們可以提前統計出來,對於最後的石子,排序一個個取就可以了。


# include <cstdlib>
# include <cstdio>

using namespace std;

const long long oo= (long long)1 << 62;
const int maxn = 1000000+200;
long long sum, ans;
long long a[maxn], b[maxn];
int n,  tot, next[maxn], pred[maxn];
long long tmp;

void del(int v)
{
	next[pred[v]] = next[v];
	pred[next[v]] = pred[v];
}

void maintain(int p)
{
	  if ((a[p] != -oo && a[pred[p]] != -oo && a[next[p]]!= -oo) &&(a[p] >= a[pred[p]] && a[p] >= a[next[p]]))
	    {
			a[p] = a[pred[p]] + a[next[p]] - a[p]; a[pred[p]] = a[next[p]] = -oo;
			del(pred[p]); del(next[p]);
			maintain(pred[p]); maintain(p); maintain(next[p]);
	    }  
}

void change(int i, int j)
{
	for (;b[i] != -oo && b[i+j] != -oo && b[i] > b[i+j]; b[i]=b[i+j]=-oo, i+=2*j)
		if (!(tot & 1)) ans += b[i+j]-b[i]; else ans -= b[i+j]-b[i];
}

void sort(int l, int r)
{
    int i = l, j = r; long long d = b[(l+r)>>1];
    for (;i <= j;)
    {
		for (;b[i] > d; i++);
		for (;b[j] < d; j--);
		if (i <= j) tmp = b[i], b[i] = b[j], b[j] = tmp, i++, j--;
	}
	if (i < r) sort(i, r);
	if (l < j) sort(l, j);
}

int main()
{
	int i, j; bool t1 = false, t2 = false;
	freopen("stone.in", "r", stdin);
	freopen("stone.out", "w", stdout);
	scanf("%d", &n); a[0] = -oo; a[n+1] = -oo;
	for (i = 1; i <= n; i++)
	{ 
	   scanf("%d", &a[i]);
	   sum += a[i];
	   if (!a[i]) a[i] = -oo; else ++tot;
    }
    if (a[1] == -oo) t1 = true; if (a[n] == -oo) t2 = true;
    for (i = 1; i <= n; i++) pred[i] = i-1, next[i] = i+1;
    for (i = 1; i <= n; i++)
    if (a[i] != -oo) maintain(i);
    for (b[0] = -oo, i = 1, j = 0; i <= n; i=next[i])
	{
		if (a[i] != -oo) b[++j] = a[i];
		else if (b[j] != -oo) b[++j] = -oo; 
	}    
	for (;b[j] == -oo;j--); n = j;
	b[0] = -oo; b[n+1] = -oo; 
	if (!t1)change(1, 1); 
	if (!t2)change(n, -1);
	sort(1, n);
	for (i = 1; i <= n && b[i] != -oo; i++)
	 if (i & 1) ans += b[i]; else ans -= b[i];
	printf("%I64d %I64d", sum+ans >> 1, sum-ans >> 1);
	return 0;
}

city

      ......


Bounce:

題意:給定序列k[n],兩個操作,一個修改操作,修改某個位置的k,一個詢問操作,詢問從i位置開始,每次跳躍到i+k[I], 問最後跳多少次可以跳出序列。

 

     正解要用動態樹的,結果無恥的用塊鏈水過了。

     把k分成sqrt(n), 統計每個位置跳出該塊的步數以及跳出去會跳到哪裏,這樣每個詢問都是sqrt(n)級別的,速度勉勉強強;


# include <cstdio>
# include <cstdlib>
# include <cmath>

using namespace std;

const int maxn = 300000;
int aux[maxn], k[maxn], head[maxn], tail[maxn], go[maxn], cost[maxn];
int n, m, size, num;

inline void change(int st, int d)
{
	int i, id = aux[st]; k[st] = d;
	for (i = st; i >= head[id]; i--)
	   if (i+k[i] > tail[id]) cost[i] = 1, go[i] = i+k[i];
	    else go[i] = go[i+k[i]], cost[i] = cost[i+k[i]]+1;
	 
}
 
void prepare()
{
	int i, j;
	size = (int) floor(sqrt(n))+1;
	for (i = 1, num = 0; i <= n; i+= size)
	  head[++num] = i, tail[num] = i+size-1;
	tail[num] = n;
	for (i = 1; i <= num; i++)
	  for (j = head[i]; j <= tail[i]; j++)
	    aux[j] = i;
	for (i = 1; i <= num; i++)
	  change(tail[i], k[tail[i]]); 
}

inline int ask(int st)
{
	int ans;
	for (ans = 0; st <= n; st = go[st])
		ans += cost[st];
	return ans;
}

int main()
{
	int st, c, i, d;
	freopen("bounce.in", "r", stdin);
	freopen("bounce.out", "w", stdout);
	scanf("%d", &n);
	for (i = 1; i <= n; i++) scanf("%d", &k[i]);
	prepare();
	scanf("%d", &m);
	for (i = 1; i <= m; i++)
	{
		scanf("%d", &c);
		if (c == 1) 
		{
			scanf("%d", &st); st++;
			printf("%d\n", ask(st));
		}
		else
		{
			scanf("%d%d", &st, &d); st++;
			change(st, d);
		}
	}
	return 0;
}

Matrix:

題意:給定b[N][N], b[I][J]= a[i][j]+a[i-1][j]+a[i][j-1]+a[i-1][j-1], 求字典序最小的A

A的元素不超過10, n<= 200;

 

    此題無恥的抄了別人的一個關於b,a和輔助數組c的公式:

     Ai,j= Ci,j + (-1)^i+j-2 *A1,1 + (-1)^i-1*A1,j + (-1)^j-1*Ai,1    (i>1,j>1)

 其中c[I][J] = B[I][J]-C[I-1][J]-C[I][J-1]-C[I-1][J-1];

 那麼,搜索第一行的數值,維護第一列的取值範圍,出現矛盾則剪枝,這樣搜就可以了。

 

吐槽:公式抄了別人的,結果那個公式的指數居然是錯的,TAT,結果一直沒調出來,wa,wa,wa,wa…………最後手算檢驗才發現公式的指數不對=。=!,抄別人的式子真不是好習慣。


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