【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…………最后手算检验才发现公式的指数不对=。=!,抄别人的式子真不是好习惯。


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