改错之日
今天开始停课集训了,还有点茫然。
今天首先改了一下noip的几个题目,就做一个总结吧。
D1T1我找规律秒出a*b-a-b,开考1min打完,又写了个完全背包暴力对拍了一波发现正确。写完第二题之后简单证明了一下就不管了。。结果2min最后发现long long问题,天不亡我。
D1T2模拟,开始搞啊搞,记了几个数组还有点麻烦,还开了个栈。出成绩之后发现炸了,看了代码发现初始化memset少了一个数组。药丸。
D1T3废了我2h结果爆零了MD。dp定义都没想出来。最后时间不够慌忙打了个暴力还错了。。
D2T1并查集判联通,10min打完。
D2T2写了个状压dp,结果是伪的,有点错误。还好数据水给了我95,舒服。
D2T3废了2.5h还是没想出来怎么线段树维护,倒是想到了动态开点233。
正解的话。
D1T3
判断无解可以记录0环,然后对于0环里的每一个点i判断dis1[i]+dis2[i]<=dis1[n]+k就行。判断之后0环可以在图中去掉。
用dis1[i]表示1到i的最短路,dis2[i]表示n到i的最短路。定义 f[i][j]表示走到i点 路径长=dis1[i]+j的方案数。无0边,把所有点按照dis1[]排序保证转移不错误。有0边,就对于0边连的两点,按拓扑序排序。总的来说就是先按dis1[]排序,再按拓扑序。
转移先枚举j,再枚举i,排序后i能到达的点均在i后,不会漏算方案。
具体的话,我改的时候 写的对于0边topsort求环并判断转移顺序。但常数巨大无比。此时发现一个优化,对于0环可以不转移,那么就可以重构图来进行转移了。改完之后卡过。
然后看了同学的。记忆搜暴快,1.5s过所有点。算法大体还是一样的。用spfa判0环,标记所有在0环的点,记忆搜的时候只要有个在0环上的点搜到n点去了直接输出-1就好。
gtmd为什么记忆搜这么强?我大概想了一下:递推的话所有状态是枚举完全的,而记忆搜可能出现很多废弃状态 ,再加一个剪枝,常数就很小了。
总结:此题考场上想到定义的话无0边70分是很好拿的,但我就是蠢。。
代码很丑,懒得挂出来丢人了。
D2T2
先说我的错误方法:f[st]表示状态为st的最小代价,并记录g[st][i]表示在st状态最小代价的情况下,树根到i的距离。转移取min{cost}并转移树。if(f[s‘]+cost<f[s])f[s]=f[s‘]+cost
当时也是自信,打完就没管了。下来之后回想了一波发现并不那么正确,慌得一匹。
不正确的话应该是树的形态,当前最优不一定以后最优。。
我还有个奇思妙想:当f[s‘]+cost==f[s]的时候,比较两棵树的最大深度,最大深度小的期望cost更小,转移树的形态。诡异。
实际上用f[i][st]表示最深一层的深度为i,状态为s。h[i][st]表示st中不含i,把i加进st的最小代价。g[s][s‘]表示s‘是s的子集,用s‘转移到s的最小边权和。
f[i][s‘]转移到f[i+1][s] 。s‘是s的子集,我们强行规定把所有的 属于s不属于s‘的的节点加在树的最后一层
即f[i+1][s]=min(f[i+1][s],f[i][s‘]+i*g[s][s‘])
那么为什么强行这么规定会正确?
考虑:对于这种规定会有误差使f[i+1][s]偏大,而我们要求的答案一定会被枚举到且精准无误。
1 #include<cstdio> 2 #include<iostream> 3 #include<algorithm> 4 #include<cstring> 5 #define ll long long 6 #define inf 0x3f3f3f3f 7 #define N 1<<12 8 using namespace std; 9 int f[13][N],g[N][N],h[13][N],d[13][13],n,m; 10 int main(){ 11 freopen("treasure.in","r",stdin); 12 freopen("treasure.out","w",stdout); 13 scanf("%d%d",&n,&m); 14 memset(d,0x3f,sizeof(d)); 15 memset(h,0x3f,sizeof(h)); 16 for(int i=1;i<=m;i++){ 17 int a,b,c; 18 scanf("%d%d%d",&a,&b,&c); 19 d[a][b]=d[b][a]=min(d[a][b],c); 20 } 21 int mst=(1<<n)-1; 22 for(int i=1;i<=n;i++){ 23 for(int j=0;j<=mst;j++){ 24 if(1<<(i-1)&j)continue; 25 for(int k=1;k<=n;k++){ 26 if(k==i)continue; 27 if(1<<(k-1)&j)h[i][j]=min(h[i][j],d[i][k]); 28 } 29 } 30 } 31 for(int s=0;s<=mst;s++){ 32 int t=s&(s-1); 33 while(t){ 34 int x=s^t; 35 for(int i=1;i<=n;i++){ 36 if(x&1<<(i-1)) 37 g[s][t]+=h[i][t]; 38 if(g[s][t]>inf)g[s][t]=inf; 39 } 40 t=s&(t-1); 41 } 42 } 43 memset(f,0x3f,sizeof(f)); 44 for(int i=1;i<=n;i++)f[1][1<<(i-1)]=0; 45 for(int i=2;i<=n;i++) 46 for(int s=0;s<=mst;s++){ 47 int t=s&(s-1); 48 while(t){ 49 int tmp; 50 if(g[s][t]<inf)tmp=(i-1)*g[s][t]; 51 else tmp=inf; 52 f[i][s]=min(f[i][s],f[i-1][t]+tmp); 53 t=s&(t-1); 54 } 55 } 56 int ans=inf; 57 for(int i=1;i<=n;i++)ans=min(ans,f[i][mst]); 58 printf("%d",ans); 59 return 0; 60 }
D2T3
从未见过这么厚颜无耻的题,听出题人说还卡常。一开始以为是n*m<=3e5,md竟然是n<=3e5,m<=3e5
noip第一次引入log数据结构竟然这么难,给未成年人的心理留下了难以磨灭的阴影。
考虑只有一行的情况。我们可以建立一棵m+q大小权值线段树,最下层节点存放人的标号,一开始把1~m区间都sum设为区间长度。走一个就把那个位置sum变成0,向上传递。最后在屁股后面加上出队这个人。查询就可以查找哪个位置前面有k个1。
(考试的时候我想到这里就能A掉他啊woc,还是道行太浅了了)
推广到n*m
n+1棵线段树,1~n表示每行1~m-1位置,n+1单独表示最后一列。
那么出队一个人(x,y),分类讨论。y==m就在第n+1棵树里面找第x个1位置得人,y<m就在第x棵树里找第y个1位置的人。
y==m的话,跟一行的情况类似,拖出来再甩到屁股后面。
y<m,在第x棵树里把那个人拖出来扔到树n+1屁股后面。把n+1棵树的第y个1位置的人拖出来甩到树x屁股后面就ok了。
具体的细节参加代码,主要是动态开点有点麻烦。
1 #include<cstdio> 2 #include<iostream> 3 #include<algorithm> 4 #include<cstring> 5 #define ll long long 6 #define N 300005 7 using namespace std; 8 int n,m,q,cnt,rt[N],ed[N]; 9 struct tree{int ls,rs,sum;ll x;}t[N*60]; 10 ll ans; 11 void pushup(int u){ 12 int l=t[u].ls,r=t[u].rs; 13 t[u].sum=t[l].sum+t[r].sum; 14 } 15 16 void query(int u,int L,int R,int pos,int k){ 17 if(L==R){ 18 if(!t[u].x){ 19 if(pos==n+1)t[u].x=(ll)L*m; 20 else t[u].x=1ll*(pos-1)*m+L; 21 } 22 ans=t[u].x;t[u].sum=0; 23 return; 24 } 25 int mid=L+R>>1; 26 if(!t[u].ls){ 27 t[u].ls=++cnt; 28 t[t[u].ls].sum=mid-L+1; 29 } 30 if(!t[u].rs){ 31 t[u].rs=++cnt; 32 t[t[u].rs].sum=R-mid; 33 } 34 if(t[t[u].ls].sum>=k)query(t[u].ls,L,mid,pos,k); 35 else query(t[u].rs,mid+1,R,pos,k-t[t[u].ls].sum); 36 pushup(u); 37 } 38 39 void update(int u,int L,int R,int l,int r,int op,ll val){ 40 if(l<=L&&R<=r){ 41 if(!op)t[u].sum=R-L+1; 42 else t[u].sum=1,t[u].x=val; 43 return; 44 } 45 int mid=L+R>>1; 46 if(!t[u].ls)t[u].ls=++cnt; 47 if(!t[u].rs)t[u].rs=++cnt; 48 if(mid>=l)update(t[u].ls,L,mid,l,r,op,val); 49 if(mid<r)update(t[u].rs,mid+1,R,l,r,op,val); 50 pushup(u); 51 } 52 53 int main(){ 54 freopen("phalanx.in","r",stdin); 55 freopen("phalanx.out","w",stdout); 56 scanf("%d%d%d",&n,&m,&q); 57 int len=m-1+q,lem=n+q; 58 //rt[1~n]为1~n行前m-1个 rt[n+1]为第m列 59 for(int i=1;i<=n;i++){ 60 rt[i]=++cnt;ed[i]=m-1;//ed记录队尾 61 update(rt[i],1,len,1,m-1,0,0); 62 } 63 rt[n+1]=++cnt;ed[n+1]=n; 64 update(rt[n+1],1,lem,1,n,0,0); 65 for(int i=1;i<=q;i++){ 66 int x,y; 67 scanf("%d%d",&x,&y); 68 if(y==m){ 69 query(rt[n+1],1,lem,n+1,x);//抽出第m列第x个 70 printf("%I64d\n",ans);ed[n+1]++; 71 update(rt[n+1],1,lem,ed[n+1],ed[n+1],1,ans);//把抽出的那个放进m列尾 72 } 73 else{ 74 query(rt[x],1,len,x,y);//抽出(x,y) 75 ed[n+1]++;update(rt[n+1],1,lem,ed[n+1],ed[n+1],1,ans);//把(x,y)放进(n,m+1) 76 printf("%I64d\n",ans); 77 query(rt[n+1],1,lem,n+1,x);//抽出(x,m) 78 ed[x]++;update(rt[x],1,len,ed[x],ed[x],1,ans);//把(x,m)放进(x,m-1) 79 } 80 } 81 return 0; 82 }
本来下午就改完了的,晚上一直优化D1T3的常数,被搞疯了。。
听说这周复习网络流?
原文:http://www.cnblogs.com/wsy01/p/7912547.html