模拟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
其实是个结论题

期望的线性性的意思是期望可以加和
感性理解一下,每个猎人死的概率相当与打多少枪打死他,别的都死光了剩下的就是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
看到这么小当然要状压了,连折半都不用,关键是怎么转移
分析题意会发现这个路径唯一是干啥的:

设\(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.不要自闭,不能丢状态

浙公网安备 33010602011771号