【hnoi2009】

 

    强烈表示被虐菜啊,被day2的题虐得跟屎一样了=。=!!!!

    hnoi难道稳定每年一到论文题么?有两道题很是在难搞,压栈,polya什么的必须要搞啊。

    ps:网上积木游戏居然仅有基哥的一篇吐槽,而图的同构计数一下子居然有三篇题解,仔细一看,ld,syj,xqz......他们一起刷的么=。=!还是老早就刷了=。=!

    

    梦幻布丁:

    题意:略

   每种颜色维护一个链表,x颜色变成y时,把x链到y的后面去, 扫描x中的布丁,对于每一个改变颜色的布丁,如果它改变后的颜色与它前面的布丁相同,ans--,如果和后边的相同,ans--,当然,如果每一次扫一遍,可能退化成O(n^2),我们可以进行启发式合并,每次扫较短的一条链,复杂度降为O(nlogn),(略证:每次如果扫到了长度为k的链表,必然合并出长度至少为2k的链表,那么对于每个布丁,顶多被扫到logn次,从布丁的角度看,复杂度是o(nlogn)的),等等,前面不是固定了只能扫y上的布丁吗?其实x,y只是代号,我们完全可以让y是x,x是y这样不就可以扫x上的布丁了?大不了以后一直把x看成y,y看成x就行了。

   ps:想这道题时总觉得应该吧合并之后的布丁删掉,结果写的时候无比纠结,涉及三个链表的删除(>.<),后来看了网上的程序才恍然大悟,合并的布丁不删又不会有问题,干嘛老是想要删掉它,这说明解题遇到问题是,先想是否可以避开这个问题,再想如何解决这个问题,可以避免走不必要的弯路。

   另外,hnoi的数据水的可以,每次归并两个链表都不会tle=。=,这种方法是可以卡的,但貌似没有这种数据(⊙o⊙)…。

 

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

using namespace std;

const int maxn=200000+5, maxm=2000000+50;
int id[maxm], c[maxn], nex[maxn+maxm],ed[maxn+maxm], len[maxn+maxm];
int n,m,ans,co; 
inline void link(int x, int y){nex[ed[x]]=y; ed[x]=y;};
inline void empty(int x){nex[x]=0; ed[x]=x; len[x]=0;};
inline void swap(int &x, int &y) {int tmp=x;x=y;y=tmp;};
inline void updata(int x, int y)
{
  int now;
  for (now=nex[x]; now!=0; now=nex[now])
  { 
    if (now-1-co>=1 &&c[now-1-co]==y) ans--;
    if (now+1-co<=n &&c[now+1-co]==y) ans--; 
  }
  for (now=nex[x]; now!=0; now=nex[now])
    c[now-co]= y;
  len[y]+= len[x]; nex[ed[y]]=nex[x]; ed[y]=ed[x]; empty(x);
}
int main()
{
  int i,d,x,y;
  freopen("pudding.in", "r", stdin);
  freopen("pudding.out", "w", stdout);
  scanf("%d%d",&n, &m);
  co=maxm;
  for (i = 1; i <= maxm; i++) ed[i]=i, nex[i]=0;
  for (i = 1; i <= n; i++) scanf("%d", &c[i]);
  for (i = 1; i <= maxm; i++) id[i]=i;
  for (link(c[1],1+co), len[c[1]]++, ans=1,i=2; i<=n;i++)
    if (c[i]==c[i-1]) link(c[i],i+co), len[c[i]]++;
    else link(c[i],i+co),ans++, len[c[i]]++;
  for (i = 1; i <= m; i++)
  {
    scanf("%d", &d);
    if (d==2) printf("%d\n", ans);
    else 
    {
      scanf("%d%d", &x,&y);
      if (x==y) continue; 
      if (len[id[x]]>len[id[y]]) swap(id[x],id[y]); 
      x=id[x];y=id[y]; if (len[x]!=0) updata(x,y);
    }
  }
  return 0;
}

通往城堡之路:

    题意:给定一个序列ai,吧ai修改为bi,使得bi中相邻两个元素之差的绝对值不超过k,a的第一个元素,最后一个元素不可改变,求最小改变量。

     

    在km顶标和差分约束上绕了好久,式子化的极丑无比,结果mt留下的是个极其无语的神级贪心调整(ms打死我我也想不出可以这么贪心啊)。

    首先将b序列初始为:bi= a1-(i-1)*k,这是每个b元素可以取得最小值。然后每一步我们选择一个位置k,将k~n抬升尽量多的高度(只抬高k~n可以么?当然可以因为如果i提升,i+1必须提升,想想我们的初始序列是什么样子的吧),如何选择k,考虑k~n中,如果有一个aj>bj,抬高j有正收益,反之有负收益,我们选择正收益最大的一段。

   显然这样贪出的解不可能继续通过抬高来减小改变量了,而降低也是不可行的,因为我们之前一定经历过那个状态了。

    另外还有一些细节需要考虑,这里就不赘述了。

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

using namespace std;

const int N = 5000+5;
const long long oo = (1LL<<60);
long long ans, up, a[N], b[N], now, maxn;
int bj,have,i,n,m,d;
inline long long ABS(long long x) {return x<0?-x:x;};
inline long long min(long long x, long long y) {return x<y?x:y;};
inline long long max(long long x, long long y) {return x>y?x:y;}; 
int main()
{
	freopen("input.txt","r", stdin);
	freopen("output.txt", "w", stdout);
	scanf("%d", &m);
	while (m--)
    {
		scanf("%d%d", &n, &d);
		for (i = 1; i <= n; i++) scanf("%I64d", &a[i]);
		if (a[n]<=a[1]+1LL*d*(n-1) && a[n]>= a[1]-1LL*d*(n-1))
		{
		  for (b[1]=a[1],i=2;i<=n;i++) b[i]=b[i-1]-d;
		  for (;b[n]!= a[n];)
          {
	    	  have=0; now=oo; maxn=-oo; bj= 0;
	  		  for (i = n; i > 1; i--)
			  {
				 if (a[i] <= b[i]) have--;
				 else have++, now=min(a[i]-b[i], now);
			     if (have>maxn && b[i]< b[i-1]+d)
			       maxn=have, bj=i, up=now;
			  }
			 up=min(up, b[bj-1]+d-b[bj]);
			 for (i = bj; i <= n; i++) b[i] += up;
			 //printf("%d %d\n", bj, up);
		 }
		 for (ans=0,i=1;i<=n;i++)
		   ans+= ABS(a[i]-b[i]);
		 //for (i = 1; i <= n; i++) printf("%d ", b[i]); printf("\n");
		 printf("%I64d\n", ans);
	   } else printf("impossible\n");
	}
	return 0;
}


有趣的数列:

题意:我们称一个长度为2n的数列是有趣的,当且仅当该数列满足以下三个条件:
(1)它是从1到2n共2n个整数的一个排列{ai} 
(2)所有的奇数项满足a1<a3<…<a[2n-1],所有的偶数项满足a2<a4<…<a[2n] 
(3)任意相邻的两项a[2i-1]与a[2i] 满足奇数项小于偶数项 

求有多少个这样的有趣序列。


打个表可以看出就是一个catalan数,至于为什么是catalan数,可以参见哲爷博客的证明。把问题转化为特殊的拓扑排序计数,然后写出dp方程,发现转移到了catalan数经典的格路问题上,这样就ok了(其实转化之后即使不知道catalan数,应该也可以推出公式了)

http://hi.baidu.com/cheezer94/blog/item/9b9610d81ba6709aa1ec9c12.html

至于catalan数的公式 Cn =C(2n,n)-C(2n,n-1); 怎么求这个很简单的,坑爹的是取mo的数不是质数,不是质数的xx用逆元乱搞了,乖乖的分解质因数搞吧。


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

using namespace std;

const int maxn = 1000000+10;
long long ans, sum;
int n, N, p;
int step[maxn*3],have[maxn*3], pr[maxn*3];
void prepare()
{
	int i,j;
	memset(step,0,sizeof(step));
	for (i = 2; i <= N; i++)
	{
		if (!step[i]) pr[++pr[0]]= i,step[i]=i;
		for (j = 1; j <= pr[0]; j++)
		{
			if (pr[j]*i > N) break;
			step[pr[j]*i]= pr[j];
			if (i%pr[j]==0) break;
		}
	}
}

int main()
{
	int i,j,x;
	freopen("input.txt", "r", stdin);
	freopen("output.txt", "w", stdout);
	scanf("%d%d", &n, &p); N=n*2;
	prepare();
	memset(have,0,sizeof(have));
	for (i = n+1; i <= N; i++) 
	  for (x=i; x>1; x/=step[x])
	    have[step[x]]++;
	for (i = 1; i <=n; i++) 
	  for (x=i; x>1; x/=step[x])
	    have[step[x]]--;
	for (sum=1,i = 1; i <= N; i++)
	  for (j = 1; j <= have[i]; j++)
	    sum= (sum*i) %p;
	ans = sum; 
	memset(have,0,sizeof(have));
	for (i = n; i <=N; i++)
	  for (x=i; x>1; x/=step[x])
	    have[step[x]]++;
	for (i = 1; i <=n+1; i++)
	  for (x=i; x>1; x/=step[x])
	    have[step[x]]--;
	for (sum=1,i = 1; i <= N; i++)
	  for (j = 1; j <= have[i]; j++)
	    sum = (sum*i)% p;
	ans = (ans-sum+p)%p;
	printf("%d", ans);
	return 0;
}


最小圈:

题意:求一个有向图的平均值最小圈。

二分+判负环什么的,应该是很好想的吧,只是题目还不准直接用bellman ford或者spfa判负环,网上一个个标程数过来,dfs,dfs,dfs......居然一个个都是用n边dfs判的负环,靠着强大的break AC了>.<,怎么能这样=。=!,不过貌似没有很靠谱的方法了,我就用涛哥的卡队列的方法水掉它了。


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

using namespace std;

const int maxn = 5000, maxm = 10000+10;
double dist[maxn], wis[maxm*2];
int n,m,top,et[maxn], que[maxn*30], linke[maxm*2],next[maxm*2],sum[maxm*2];
bool step[maxn];
void link(int x, int y, double z)
{
	++top; next[top]=linke[x]; linke[x]=top; sum[top]=y; wis[top]=z;
}
void change(double lim)
{
   for (int i = 1; i <= top; i++) wis[i]-= lim;
}
bool spfa(double lim)
{
	change(lim);
	int head=1,tail=1,ke,x; 
	memset(step,false,sizeof(step)); memset(et,0,sizeof(et)); memset(dist,127,sizeof(dist));
	dist[1]=0; step[1]=true; que[1]=1;
	for (;head<=tail;step[que[head++]]=false)
		for (ke=linke[x=que[head]]; ke!=0; ke=next[ke])
			if (dist[sum[ke]]>dist[x]+wis[ke])
			{
				dist[sum[ke]]=dist[x]+wis[ke];
				if (!step[sum[ke]]) 
				{
					step[que[++tail]=sum[ke]] = true;
					if (++et[sum[ke]]>15) {change(-lim); return true;} 
				}
			}
	change(-lim);
	return false;
}
int main()
{
	int i,x,y; double z,l,r,mid;
	freopen("input.txt","r",stdin);
	freopen("output.txt","w",stdout);
	scanf("%d%d", &n, &m);
	for (i = 1; i <= m; i++) 
	{
		scanf("%d%d%lf",&x,&y,&z);
		link(x,y,z);
	}
	for (l=-1e7,r=1e7; r-l>1e-9;)
		if (spfa(mid=(l+r)/2)) r=mid; 
		else l=mid;
	printf("%.8lf", l);
	return 0;
}

永无乡:

     吐槽啊吐槽,一开始以为每个岛上最多是个三元环才会满足条件,结果好happy的写了dfs找环+环状dp,结果WA的一塌糊涂(TAT),撞的头破血流之后才发现,只要是从一个顶点上连出很多三元环的“花朵形状”都可以=。=!,这如果还缩环,判到世界末日大概都搞不完......

    然后在一篇blog上淘到了一个漂亮的仙人掌树dp法。

    观察题目给出的图可以发现,每条边属于且仅属于一个环=。=!,这个树形dp极大的好处,我们可以每次用回边转移,就能保证dp的正确性了。

    

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

using namespace std;

const int oo=1073741819,maxm = 200000*2+10, maxn = 100000+10;
int top,next[maxm], linke[maxm], sum[maxm];
int time,n,m,a[maxn],dfn[maxn],pre[maxn],f[maxn], g[maxn];
int u0,u1,v0,v1;
inline int max(int x, int y) {return x>y?x:y;};

void link(int x, int y)
{
	++top; next[top]=linke[x]; linke[x]=top; sum[top]=y;
}

void dfs(int x)
{
    int k;dfn[x]=++time;
	for (k=linke[x]; k; k=next[k])
	if (!dfn[sum[k]])
		pre[sum[k]]= x, dfs(sum[k]);
	f[x] = a[x];
	int j;
	for (k=linke[x]; k; k=next[k])
	if (dfn[sum[k]]>dfn[x] && pre[sum[k]]!= x)
	{
		u0 = 0; u1 = 0;
		for (j=sum[k];j!=x;j=pre[j])
		{
			v0= u1+g[j]; v1= u0+f[j];
			u0= v0; u1= max(v0, v1);
		}
		g[x]+= u1;
		u0 = -oo; u1 = 0;
		for (j=sum[k];j!=x;j=pre[j])
		{
			v0= u1+g[j]; v1= u0+f[j];
			u0= v0; u1= max(v0, v1);
		}
		f[x]+= u0;
	}
}

int main()
{
	int i,x,y;
	freopen("input.txt", "r", stdin);
	freopen("output.txt", "w", stdout);
	scanf("%d%d", &n, &m);
	for (i = 1; i <= m; i++)
	{
		scanf("%d%d", &x, &y);
		link(x,y); link(y,x);
	}
	for (i = 1; i <= n; i++)
	     scanf("%d", &a[i]);
	dfs(1);
	printf("%d", max(f[1], g[1]));
	return 0;
}

 
图的同构计数: 数论弱菜伤不起啊,鉴于后面专门有计划弄数论,这里就先压栈吧。


双递增序列: 

题意:略

       再次吐槽,又想到ldl那套无比诡异的伪装成noip的hnoi题。

       一开始还没想起来,结果想到一个贪心调整,觉得思路似曾相识,然后翻到了那套题目。

好吧,这道题目诡异之处在于,当时写的贪心调整后来发现调整部分的思路是不对的,而且.....我在三个地方都写错了,以至于调整部分都没有进入=。=!这样都对了?ldl的数据水了?好吧,hnoi的数据也过了,诡异的是我用c++打一遍之后c++的程序错了,后来照着pascal的打了一遍居然有对了(看了半天两次写的程序本质一样啊抓狂),O__O"…

      至于那个dp就不想讲了,这道题真是永远的痛额。

      不知道是数据太水还是我的想法是错的,我觉得只要能贪出两条长度不等的链来,就一定能调整出长度相等的两条链,自己证明不出来,也不知道对不对,求大牛指证。

      纯粹骗分?忽略下面的代码

 

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

using namespace std;

const int maxn = 3000;
int ta,tb,qa,qb,inc,ia[maxn],ib[maxn];
int n,task,a[maxn],tmp;
bool use[maxn];
bool check()
{
	int i,j;qa=qb=-1;inc=0;ta=0;tb=0;
	//memset(ib,0,sizeof(ib)); memset(ia,0,sizeof(ia));
	for (qa=a[1],i=2; i <= n; i++)
	{
	  if (a[i]<=qa&& a[i]<=qb) return false;
	  else if (a[i]>qa&&a[i]<=qb) qa=a[i];
	  else if (a[i]<=qa&&a[i]>qb) qb=a[i];
	  else if (qa>qb) qa=a[i];
	  else qb=a[i];
	}
	return true;

}

int main()
{
	int i;
	freopen("input.txt", "r", stdin);
	freopen("output.txt", "w", stdout);
	scanf("%d", &task);
	while (task--)
	{
		scanf("%d", &n);
		for (i = 1; i <= n; i++) scanf("%d", &a[i]);
		for (i = 1; i <= n; i++) a[i]++;
		if (check()) printf("Yes!\n"); else printf("No!\n");
	}
	return 0;
}





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