强烈表示被虐菜啊,被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; }