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





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