noip模拟6[辣鸡·模板·大佬·宝藏]
这怕不是学长出的题吧
这题就很迷
这第一题吧,正解竟然是O(n2)的,我这是快气死了,考场上一直觉得aaaaa n2过不了过不了,
我就去枚举边了,然后调了两个小时,愣是没调出来,然后交了个暴力,就走了15pts
然后我就淦第二题,这第一眼扫过去,就觉得是树链剖分,然后连复杂度都没算,就生生的码了一个小时,
给我弄傻了,好像复杂度是(8n+?????nlogn)的,然后看了看最坏是(n2logn)的,不卡死你才怪!!!
然后这第三题吧,是这场考试中最让我后悔的一道题,看到概率期望就害怕,然后最后去干他,然后就没打完,然后还没思路
但是我总结到一个经验
有关有规律的随机数的问题,完全不需要考虑什么后效性,因为,是随机的,情况多去了
第四题,这是个非常非常狗的题,狗不狗先放一边,问题是他竟然是 李煜东蓝书上的原题,我没看到,但有人看过了。哇呜呜
还有一个事,这是我第一次在考场上用到对拍程序,还是有点小小的成就感的;
然后就是正解啦,这次改题非常顺利,没有浪费一丁点的时间
T1辣鸡
题目大意:给你张图,上面有好多为1的点,求这些点中,能够相邻并且利用对角线相连的边
就这样连接
然后这些点是通过一个一个小矩形给出来的,每次给出左下角和右上角的坐标,然后就可以计算了
第一眼看到这个题,我就直接蒙了一会,因为题面给了一个“曼哈顿距离”,就把我弄傻了
然后就打了个枚举点的极其暴力的打法,验证了我的猜想是正确的
然后我就开始想正解,显然我意识到了先算自己矩形内部的连边,再算两两矩形之间的连边
然后我想到了可以一个矩形一个矩形的枚举,然而,我在两秒钟之后就否掉了这个想法,因为,我认为复杂度太高了
可是苍天不公,这竟然是正解!!!!
首先枚举每个矩形内部的连边,O(1)计算 (y2-y1)*(x2-x1);
在枚举矩形之间的连边之前要先排序,按照x1升序,y1升序,排列
然后再枚举,注意这时候的各种判断就来了
自己推吧,主要注意判断两个矩形对角的时候,这个要单独判断,而且,不能与其他情况混起来算
然后先i-n j=i+1~~n这样枚举,可以有减支的机会,如果向前枚举,那么只能不断continue,而不能break
这样时间就足以满足AC这道题了!!~~~
然后贴上代码,代码没有注释,自己看思路,然后理解代码,能不看就别就看
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #include<bits/stdc++.h> 2 using namespace std; 3 #define int long long 4 #define re register int 5 const int N=100005; 6 struct node{ 7 int xa,xb,ya,yb; 8 int zs,zx,ys,yx; 9 }pos[N]; 10 int n,ans; 11 bool com(node a,node b){ 12 if(a.xa==b.xa)return a.ya<b.ya; 13 return a.xa<b.xa; 14 } 15 signed main(){ 16 scanf("%lld",&n); 17 for(re i=1;i<=n;i++){ 18 scanf("%lld%lld%lld%lld",&pos[i].xa,&pos[i].ya,&pos[i].xb,&pos[i].yb); 19 } 20 sort(pos+1,pos+n+1,com); 21 for(re i=1;i<=n;i++){ 22 ans+=2*(pos[i].xb-pos[i].xa)*(pos[i].yb-pos[i].ya); 23 for(re j=i+1;j<=n;j++){ 24 if(pos[j].xa-1>pos[i].xb)break; 25 if(pos[i].ya-1>pos[j].yb)continue; 26 if(pos[j].ya-1>pos[i].yb)continue; 27 if(pos[j].xa-1==pos[i].xb&&(pos[i].ya-1==pos[j].yb||pos[j].ya-1==pos[i].yb)){ 28 ans++; 29 continue; 30 } 31 if(pos[j].xa-1==pos[i].xb){ 32 ans+=2*(min(pos[i].yb,pos[j].yb)-max(pos[i].ya,pos[j].ya)); 33 if(pos[i].ya!=pos[j].ya)ans++; 34 if(pos[i].yb!=pos[j].yb)ans++; 35 continue; 36 } 37 if(pos[j].ya-1==pos[i].yb||pos[i].ya-1==pos[j].yb){ 38 ans+=2*(min(pos[i].xb,pos[j].xb)-max(pos[i].xa,pos[j].xa)); 39 if(pos[i].xa!=pos[j].xa)ans++; 40 if(pos[i].xb!=pos[j].xb)ans++; 41 continue; 42 } 43 } 44 } 45 printf("%lld",ans); 46 }
T2模板
题目大意:给你一棵树,每次任选一个节点,将这个节点到根的路径上都添加一个颜色,当然,每个点最多添加k[i]个颜色,最后让你统计,每个点的颜色种类数
我第一眼:卧槽!!
我第二眼:这不是链!!??
我第三眼:剖分!!没的商量
然后我就风风火火的码了一个小时的树链剖分,然后信心满满的期待着满分,等来的却是0
对!他竟然管这题叫模板!!!怎么敢的
其实这个题的正解是线段树的启发式合并,其实也算是广义的线段树合并,因为他并没有直接去合并两颗线段树
而是不断的向其中一棵树插入另一棵树的节点,然后统计信息,因为这个题要统计的东西有点麻烦
既然每次向某一个节点插入颜色,那我们就先向这个节点插入,那么就“启发”你要向上合并了
好像我还想到了一点点正解,因为我读代码的时候不知道为什么要找到这个点的重儿子
后来才明白,因为这样的话可以尽量省去时间复杂度(这里的重儿子不是子树大小最大,而是子树中添加的颜色最多)时间就被省去好多
然后还要动态开点,因为这个空间复杂度实在是太高了,而且,对于每一次加颜色来说,只用到了线段树上的一条链,空间也大大优化了
还有就是统计颜色种类的问题
我们按照时间去建立一颗线段树,然后每个节点都要存放两个变量,颜色种类和所有颜色的数量
这样就符合了线段树的建立要求没可以进行上下转移,可以优化时间
同种颜色我们只能统计一次,按照常人的思路,都会去统计每种颜色第一次出现的时间,当然你要去统计最后一次也没人管你,就改个符号就好了
不对不对统计最后一次是不可以的,因为我们可以加的颜色数量是有限的,所以要统计第一次
然后每次合并的时候都把这个颜色的首次出现的时间更新;
这时候就和普通的线段树合并有不一样的了
普通的线段树合并,只是简单的将两颗子树的信息合并在一起,没有办法更新这颗树上原来最早的这个颜色的权值
所以我们带着要合并到的那个根去遍历另外一颗树,然后到了叶子节点,如果叶子节点存在,那就向我们带着的那个根中插入
在插入的过程中更新某种颜色的第一次出现的位置
这样就可以想普通线段树一样查询答案了;
代码(无注释)
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #include<bits/stdc++.h> 2 using namespace std; 3 #define re register int 4 const int N=100005; 5 int n,m; 6 int to[N*2],nxt[N*2],head[N],rp; 7 int k[N]; 8 struct node{ 9 int x,c; 10 }dem[N]; 11 int lsh[N],lh; 12 void add_edg(int x,int y){ 13 to[++rp]=y; 14 nxt[rp]=head[x]; 15 head[x]=rp; 16 } 17 vector<int> mp[N]; 18 int rt[N]; 19 struct noda{ 20 int num[N*80],kin[N*80]; 21 int ls[N*80],rs[N*80]; 22 int seg; 23 int pre[N],fro[N],tot; 24 void pushup(int x){ 25 num[x]=num[ls[x]]+num[rs[x]]; 26 kin[x]=kin[ls[x]]+kin[rs[x]]; 27 } 28 void ins(int &x,int l,int r,int pos,int ki,int nu){ 29 if(!x)x=++seg; 30 if(l==r){ 31 kin[x]=ki; 32 num[x]=nu; 33 return ; 34 } 35 int mid=l+r>>1; 36 if(pos<=mid) ins(ls[x],l,mid,pos,ki,nu); 37 else ins(rs[x],mid+1,r,pos,ki,nu); 38 pushup(x); 39 } 40 int query(int x,int l,int r,int siz){ 41 if(!x||!siz)return 0; 42 if(l==r)return kin[x]; 43 int mid=l+r>>1,res=0; 44 if(ls[x]&&siz<num[ls[x]])res+=query(ls[x],l,mid,siz); 45 else{ 46 res+=kin[ls[x]]; 47 res+=query(rs[x],mid+1,r,siz-num[ls[x]]); 48 } 49 return res; 50 } 51 void cns(int x,int t,int co){ 52 if(!pre[co]){ 53 fro[++tot]=co; 54 pre[co]=t; 55 ins(rt[x],1,m,t,1,1); 56 return ; 57 } 58 if(pre[co]<t){ 59 ins(rt[x],1,m,t,0,1); 60 return ; 61 } 62 if(pre[co]>t){ 63 ins(rt[x],1,m,pre[co],0,1); 64 ins(rt[x],1,m,t,1,1); 65 pre[co]=t; 66 return ; 67 } 68 } 69 void merge(int x,int l,int r,int p){ 70 if(!x)return ; 71 if(l==r){ 72 if(num[x])cns(p,l,dem[l].c); 73 return ; 74 } 75 int mid=l+r>>1; 76 merge(ls[x],l,mid,p); 77 merge(rs[x],mid+1,r,p); 78 } 79 void cl(){ 80 while(tot)pre[fro[tot--]]=0; 81 } 82 }xds; 83 int son[N],siz[N]; 84 int ans[N]; 85 void dfs1(int x,int f){ 86 siz[x]=mp[x].size()+1; 87 for(re i=head[x];i;i=nxt[i]){ 88 int y=to[i]; 89 if(y==f)continue; 90 dfs1(y,x); 91 siz[x]+=siz[y]; 92 if(siz[y]>=siz[son[x]])son[x]=y; 93 } 94 } 95 void dfs(int x,int f){ 96 //cout<<x<<" "<<"sb"<<endl; 97 for(re i=head[x];i;i=nxt[i]){ 98 int y=to[i]; 99 if(y==f||y==son[x])continue; 100 dfs(y,x);xds.cl(); 101 } 102 //cout<<"sb"<<endl; 103 if(son[x])dfs(son[x],x); 104 rt[x]=rt[son[x]]; 105 for(re i=0;i<mp[x].size();i++) 106 xds.cns(x,mp[x][i],dem[mp[x][i]].c); 107 for(re i=head[x];i;i=nxt[i]){ 108 int y=to[i]; 109 if(y==f||y==son[x])continue; 110 xds.merge(rt[y],1,m,x); 111 } 112 ans[x]=xds.query(rt[x],1,m,k[x]); 113 } 114 signed main(){ 115 scanf("%d",&n); 116 for(re i=1;i<n;i++){ 117 int x,y; 118 scanf("%d%d",&x,&y); 119 add_edg(x,y); 120 add_edg(y,x); 121 } 122 for(re i=1;i<=n;i++)scanf("%d",&k[i]); 123 scanf("%d",&m); 124 for(re i=1;i<=m;i++){ 125 scanf("%d%d",&dem[i].x,&dem[i].c); 126 lsh[i]=dem[i].c; 127 } 128 sort(lsh+1,lsh+m+1); 129 lh=unique(lsh+1,lsh+m+1)-lsh-1; 130 for(re i=1;i<=m;i++){ 131 dem[i].c=lower_bound(lsh+1,lsh+lh+1,dem[i].c)-lsh; 132 mp[dem[i].x].push_back(i); 133 } 134 //cout<<"sb"<<endl; 135 dfs1(1,0); 136 dfs(1,0); 137 int q; 138 scanf("%d",&q); 139 for(re i=1;i<=q;i++){ 140 int x; 141 scanf("%d",&x); 142 printf("%d\n",ans[x]); 143 } 144 }
然后就是
T3大佬
题面:辣鸡ljh NOI之后就退役了,然后就滚去学文化课了。
他发现katarina大佬真是太强了,于是就学习了一下katarina大佬的做题方法。
比如这是一本有n道题的练习册,katarina大佬每天都会做k道题。
第一天做第1~k题,第二天做第 2~k+1
题……第n-k+1天做第n-k+1~n
道题。
但是辣鸡 ljh 又不想太累,所以他想知道katarina大佬做完这本练习册的劳累度。
每道题有它的难度值,假设今天katarina大佬做的题目中最大难度为t,那么今天katarina大佬的劳累度就是w[t],做完这本书的劳累值就是每天的劳累值之和。
但是辣鸡ljh一道题都不会,自然也不知道题目有多难,他只知道题目的难度一定在1~m之间随机。
他想让即将参加 NOIP 的你帮他算算katarina大佬做完这本书的劳累值期望
这个题就很有意思,看上去哇塞,他好像有后效性诶,然后我就果断弃掉了,后来发现,以后不能随便放弃某个题
我们先只考虑考虑一天,每个题的难度取值都有m种可能性(先不考虑w)
那么最大值<=1的概率就是g[1]=(1/m)k
<=2的概率就是g[2]=(2/m)k
<=m的概率就是g[m]=1
这不相当于一个前缀和嘛,f[i]=g[i]-g[i-1]
然后我们就把概率求出来了,简单暴了,然后我们就可以直接拿概率*w[i]然后求和
然后再乘上一个(n-k+1)因为一共有这么多天
代码
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #include<bits/stdc++.h> 2 using namespace std; 3 #define re register int 4 const int N=505; 5 const int mod=1000000007; 6 int n,m,k; 7 int f[N],g[N],ink; 8 int w[N],ans; 9 int ksm(int x,int y){ 10 int ret=1; 11 while(y){ 12 if(y&1)ret=1ll*ret*x%mod; 13 x=1ll*x*x%mod; 14 y>>=1; 15 } 16 return ret; 17 } 18 signed main(){ 19 scanf("%d%d%d",&n,&m,&k); 20 ink=ksm(ksm(m,k),mod-2); 21 for(re i=1;i<=m;i++){ 22 g[i]=1ll*ksm(i,k)*ink%mod; 23 f[i]=(g[i]-g[i-1]+mod)%mod; 24 scanf("%d",&w[i]); 25 ans=(1ll*ans+1ll*w[i]*f[i]%mod)%mod; 26 } 27 if(k>n)ans=0; 28 ans=1ll*ans*(n-k+1)%mod; 29 printf("%d",ans); 30 }
然后就
T4宝藏
一眼就看出来这个题是状压,然后就不知道该怎么办了
毕竟这么小的数据范围,我还能想到啥
好像这个题暴搜/模拟退火都能过诶
然后我还是老老实实的去状压,
设dp[i][s]为目前最大深度为i,已经可以达到的点的状态为s
然后就有一个转移方程f[i][j]=min(f[i-1][k]+cos(k,j)*(i-1);
还有一些需要预处理的东西,比如由k能否转移到j,比如转移的最小花费是多少;
还有这个题最坑的地方------有重边!!!!
还好我机智,加了一个判断
然后关于为什么可以直接乘(i-1)而不需要考虑其他深度小于(i-1)的点,
因为如果他乘上(i-2)可以造成最优解,那么他一定不会停留在i-2这个点,就会向前转移
所以不需要考虑这些东西
代码
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #include<bits/stdc++.h> 2 using namespace std; 3 //#define int long long 4 #define re register int 5 const int N=1005; 6 const int S=(1<<12)+10; 7 int n,m; 8 int to[N*2],nxt[N*2],val[N*2],head[N],rp; 9 int edg[15][15]; 10 void add_edg(int x,int y,int z){ 11 to[++rp]=y; 12 val[rp]=z; 13 nxt[rp]=head[x]; 14 head[x]=rp; 15 if(edg[x][y]>z)edg[x][y]=z; 16 } 17 int dp[15][S],minn[S][15],can[S]; 18 void getmin(){ 19 int now[15],dre[15],dnt,cnt,x; 20 for(re i=1;i<(1<<n);++i){ 21 dnt=cnt=0;can[i]=i;x=i; 22 for(re j=1;j<=n;++j) 23 if((i>>(j-1))&1)now[++cnt]=j; 24 else dre[++dnt]=j; 25 for(re j=1;j<=dnt;++j){ 26 for(re k=1;k<=cnt;++k){ 27 if(edg[dre[j]][now[k]]!=0x3f3f3f3f){ 28 //if(edg[dre[j]][now[k]]>10000)cout<<edg[dre[j]][now[k]]<<endl; 29 can[i]|=(1<<(dre[j]-1)); 30 minn[i][dre[j]]=min(minn[i][dre[j]],edg[dre[j]][now[k]]); 31 } 32 } 33 } 34 } 35 } 36 int value(int x,int y){ 37 //cout<<x<<" "<<y<<endl; 38 int z=(y^x),ret=0; 39 //cout<<z<<" "<<endl; 40 for(re i=1;i<=n;++i) 41 if((1<<(i-1))&z){ 42 ret+=minn[x][i]; 43 //cout<<minn[x][i]<<endl; 44 } 45 return ret; 46 } 47 signed main(){ 48 scanf("%d%d",&n,&m); 49 memset(edg,0x3f,sizeof(edg)); 50 memset(minn,0x3f,sizeof(minn)); 51 for(re i=1;i<=m;++i){ 52 int x,y,z; 53 scanf("%d%d%d",&x,&y,&z); 54 add_edg(x,y,z); 55 add_edg(y,x,z); 56 //cout<<edg[x][y]<<" "<<edg[y][x]<<endl; 57 } 58 //cout<<(5^1)<<endl; 59 getmin(); 60 //cout<<can[1]<<endl; 61 memset(dp,0x3f,sizeof(dp)); 62 for(re i=1;i<=n;++i)dp[1][(1<<(i-1))]=0; 63 int ans=dp[1][(1<<n)-1]; 64 //cout<<0x3f3f3f3f<<endl;cout<<dp[1][0]<<endl; 65 for(re i=2;i<=n;++i){ 66 for(re j=1;j<(1<<n);++j){ 67 for(re k=1;k<(1<<n);++k){ 68 if((j&can[k])!=j)continue; 69 if((k&j)!=k)continue; 70 if(dp[i-1][k]==0x3f3f3f3f)continue; 71 dp[i][j]=min(dp[i][j],dp[i-1][k]+value(k,j)*(i-1)); 72 } 73 } 74 //cout<<ans<<endl; 75 ans=min(ans,dp[i][(1<<n)-1]); 76 } 77 printf("%d",ans); 78 }