模拟33 考试总结

别问32在哪里,问就是没改完,反正前面都咕几十个了

考试经过

开局状态极差,一直在由于昨天T2自闭,看了看3个题似乎只有T2最可做
很快想到线段树合并,但是想了半个小时并不认为动态开点可以维护附加信息,试着写了下发现板子忘的差不多了,于是弃掉看T1,期望,直接跑路,对着T3的样例看了半天,啥也看不出来,然后惊人的发现已经八点多了。。。
草(一种植物)
我还没动键盘呢
果断投身T2的测试点分治,昏昏沉沉打完40分九点半了菜的真实,打T1爆搜发现假掉,于是放弃20打45(大雾),靠某种神奇力量在\(10:18\)调出了状压,最后靠信仰在T3printf("%lld",rand()),光荣爆零
期望:45+40+0=80
实际:30+9+0=39
T1人傻常数大,T2有个细节没想到,挂没了

没什么好难过的,没好好考,多烂都活该

T1.Hunter

其实是个结论题
image
期望的线性性的意思是期望可以加和
感性理解一下,每个猎人死的概率相当与打多少枪打死他,别的都死光了剩下的就是1号了,所以直接相加,后面其实也好理解,但是我就是发现不了
打出来简单到爆炸

#include <bits/stdc++.h>
using namespace std;
#define int long long
const int mod=998244353;
const int N=100050;
int w[N];
inline int ksm(int x,int y)
{
   int s=1;x%=mod;
   for(;y;y>>=1)
   {
      if(y&1)s=s*x%mod;
      x=x*x%mod;
   }
   return s;
}
inline int ny(int x)
{
   return ksm(x,mod-2)%mod;
}
signed main()
{
   int n;cin>>n;
   for(int i=1;i<=n;i++)scanf("%lld",&w[i]);
   int ans=1;
   for(int i=2;i<=n;i++)ans=(ans+w[i]*ny(w[i]+w[1])%mod)%mod;
   cout<<ans;
   return 0;
}

其实有时候期望没多难,不要一看就害怕!

T2.Defence

又是数据结构,又是一碰就死
答案的细节要注意,答案不一定是连续0的数量,而是它和「左端点的前缀0和右端点的后缀0的和」取\(min\)的值,因为前后两端都只能从一端处理,所以没那么简单
只有一个点的时候貌似是个简单线段树就能维护的,正解是线段树合并
其实没多难维护,合时候到了叶子节点处理就行了,\(pushup\)都是一个
如果直接维护0的话,由于动态开点没有建树,所以空结点不能不管,而是相当与一段全是1,每次遇到空结点给他建出来,把信息维护到,感觉空间好像不太稳但实测啥事没有
还有时间复杂度,只要没有区间操作,合并都是\(nlogn\)的,尽管看着很假,相信科学
以及各种细节,码力弱,没办法,只能对着暴力拍,注意无解判断



#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N=100050;
struct node{
   int from,to,next;
}a[2*N];
int head[N],mm=1;
inline void add(int x,int y)
{
   a[mm].from=x;a[mm].to=y;
   a[mm].next=head[x];head[x]=mm++;
}
const int NN=40*N;int num;int n,m,q;
int ls[NN],rs[NN],sum[NN],l0[NN],r0[NN];
inline int newpo(int l,int r)
{
   int x=++num;
   sum[x]=r-l+1;l0[x]=r-l+1;r0[x]=r-l+1;
   return x;
}
inline void qi(int x,int l,int r)
{
   int mid=(l+r)>>1;
   if(!ls[x])ls[x]=newpo(l,mid);
   if(!rs[x])rs[x]=newpo(mid+1,r);
   if(l0[ls[x]]==mid-l+1)l0[x]=l0[ls[x]]+l0[rs[x]];
   else l0[x]=l0[ls[x]];
   if(r0[rs[x]]==r-(mid+1)+1)r0[x]=r0[rs[x]]+r0[ls[x]];
   else r0[x]=r0[rs[x]];
   sum[x]=max(max(sum[ls[x]],sum[rs[x]]),r0[ls[x]]+l0[rs[x]]);
}
inline int add(int x,int l,int r,int p)
{
   if(!x)x=newpo(l,r);
   if(l==r)
   {
     sum[x]=l0[x]=r0[x]=0;
     return x;
   }
   int mid=(l+r)>>1;
   if(p<=mid)ls[x]=add(ls[x],l,mid,p);
   else rs[x]=add(rs[x],mid+1,r,p);
   qi(x,l,r);
   return x;
}
inline int merge(int x,int y,int l,int r)
{
   if(!x)return y;
   if(!y)return x;
   if(l==r)
   {
      if(sum[x]&sum[y])sum[x]=l0[x]=r0[x]=1;
      else sum[x]=l0[x]=r0[x]=0;
      return x;
   }
   int mid=(l+r)>>1;
   ls[x]=merge(ls[x],ls[y],l,mid);
   rs[x]=merge(rs[x],rs[y],mid+1,r);
   qi(x,l,r);
   return x;
}
vector <int> w[N];bool v[N];
int ans[N],mp[N];
void dfs(int x)
{
   v[x]=1;
   if(!mp[x])mp[x]=newpo(1,m);
   for(int i=head[x];i;i=a[i].next)
   {
      int y=a[i].to;
      if(v[y])continue;
      dfs(y);
      mp[x]=merge(mp[x],mp[y],1,m);
   }
   for(int i=0;i<w[x].size();i++)
     add(mp[x],1,m,w[x][i]);
   ans[x]=max(sum[mp[x]],l0[mp[x]]+r0[mp[x]]);
   if(ans[x]>=m)ans[x]=-1;//注意这里大于号,因为全是1的话答案会是两倍区间长,因为你取了两边的之和,两边都是一个区间长
}
signed main()
{
   cin>>n>>m>>q;
   for(int i=1;i<=n-1;i++)
   {
      int x,y;scanf("%lld%lld",&x,&y);
      add(x,y);add(y,x);
   }
   for(int i=1;i<=q;i++)
   {
      int x,y;scanf("%lld%lld",&x,&y);
      w[x].push_back(y);
   }
   dfs(1); 
   for(int i=1;i<=n;i++)
   {
      printf("%lld\n",ans[i]);
   }
   return 0;
}

线段树合并之类的复杂度问题还要多看看

T3.Connect

看到这么小当然要状压了,连折半都不用,关键是怎么转移
分析题意会发现这个路径唯一是干啥的:
image
\(f_{i,s}\)代表当前已经选的点集为\(s\),链的末端点为\(i\)时最大可以保留的边权总和
那么转移就有两种状态:
一种是在链的末端再挂一个点,这种情况肯定合法,贡献是新点\(j\)和原来的端点\(i\)之间的距离,直接枚举\(i\)相连的点转移,转移后端点改变
另一种是往链上再挂一个联通块,首先保证合法,即新点集与\(s\)无交,这种情况贡献是联通块内部边权之和加上联通块到\(s\)的边权,前者可以预处理,后者只有一条边

注意这里转移时只考虑联通块挂在端点\(i\)的情况,因为如果挂在别的地方一定会被别的\(i\)计算到,势必重复,所以转移时的总共现就是联通块\(j|i\)内部的权值和

我因为没理清楚这个狂T不止……
初始化\(f_{1,1}=0\),其余负无穷,最终答案就是图上所有边权之和减去最大的\(f_{s,n}\)

#include <bits/stdc++.h>
using namespace std;
#define register register
int dis[16][16],n,m;
int sum[1<<16],c[16];
int f[1<<16][20];
signed main()
{
	cin>>n>>m;
	memset(dis,128,sizeof(dis));
	for(int i=1;i<=m;i++)
	{
	   int x,y,z;scanf("%d%d%d",&x,&y,&z);
	   dis[x][y]=max(dis[x][y],z);
	   dis[y][x]=max(dis[y][x],z);
	}
	for(register int i=1;i<=(1<<n)-1;i++)
	{
	   for(register int j=1;j<=n;j++)
	    if((i>>(j-1))&1)
	     for(register int k=1;k<=n;k++)
	     {
	        if(dis[j][k]<0)continue;
	        if((i>>(k-1))&1)sum[i]+=dis[j][k];
	     }
	   sum[i]>>=1;
   }
   memset(f,128,sizeof(f));
   f[1][1]=0;
   for(register int i=1;i<=n;i++)
   {
      for(register int s=1;s<=(1<<n)-1;s++)
      {
      	if(f[s][i]<0)continue;
      	if(!((s>>(i-1))&1))continue;
      	for(register int j=1;j<=n;j++)
      	{
      	   if(dis[i][j]<0)continue;
      	   if((s>>(j-1))&1)continue;
      	   f[s|(1<<(j-1))][j]=max(f[s|(1<<(j-1))][j],f[s][i]+dis[i][j]);
   		}
   	   int ss=s^((1<<n)-1);
   		for(register int j=ss;j;j=(j-1)&ss)
            f[s|j][i]=max(f[s|j][i],f[s][i]+sum[j|(1<<(i-1))]);
      }
   }
   int ans=0;
   for(register int i=1;i<=(1<<n)-1;i++)ans=max(ans,f[i][n]);
   cout<<sum[(1<<n)-1]-ans;
   return 0;
}  

两重循环枚举状态+子集的复杂度是\(O(3^n)\),总复杂度\(n\times 3^n\)
另外学了一手不重复枚举子集的高级操作

考试总结

1.考一场忘一场,千万不能被之前的比赛影响状态
2.果断点,有了分先打上,别最后后悔不过来,看着干着急
3.想题想仔细,别想当然
4.不要自闭,不能丢状态

posted @ 2021-08-08 19:57  D'A'T  阅读(46)  评论(0)    收藏  举报