T1Hunter T2Defence T3Connect
T1 Hunter

考场上一看期望直接状压拿了$45pts$跑了。结果正解只用$4$行?

把问题转化为一号猎人之前死的猎人数的期望加一。

期望的线性性。

对每个猎人$i$,$w_i+w_1$种情况中有$w_i$种死于一号猎人之前,故期望为$\frac{w_i}{w_i+w_1}$。

枚举累加即可。

$code:$

2021.8.8考试总结[NOIP模拟33]_#define2021.8.8考试总结[NOIP模拟33]_c++_02
 1 #include<bits/stdc++.h>
 2 #define rin register signed
 3 #define int long long
 4 using namespace std;
 5 const int NN=1e5+5,p=998244353;
 6 int n,w[NN],f[1<<20],inv[1<<20],iv[NN],ans;
 7 inline int read(){
 8     int x=0,f=1; char ch=getchar();
 9     while(ch<'0'||ch>'9'){ if(ch=='-') f=-1; ch=getchar(); }
10     while(ch>='0'&&ch<='9'){ x=(x<<1)+(x<<3)+(ch^48); ch=getchar(); }
11     return x*f;
12 }
13 inline void write(int x){
14     char ch[20]; int len=0;
15     if(x<0) putchar('-'), x=~x+1;
16     do{
17         ch[len++]=x%10+(1<<5)+(1<<4);
18         x/=10;
19     }while(x);
20     for(rin i=len-1;i>=0;--i) putchar(ch[i]);
21     return;
22 }
23 inline int qpow(int a,int b){
24     int res=1;
25     while(b){
26         if(b&1) res=res*a%p;
27         a=a*a%p;
28         b>>=1;
29     }
30     return res;
31 }
32 signed main(){
33     n=read(); f[0]=1;
34     for(int i=1;i<=n;i++) w[i]=read(), iv[i]=qpow(w[i],p-2);
35     for(int i=2;i<=n;i++)
36         (ans+=w[i]*qpow(w[1]+w[i],p-2)%p)%=p;
37     write(ans+1); putchar('\n');
38     return 0;
39 }
T1

 

T2 Defence

不难想到最优策略是先用法术再用符咒,而最后答案是前缀零,后缀零的和与区间中最长零串取$min$。动态开点线段树向上合并即可。

以上思路是考场看题$10min$后出的。然后线段树假上加假,$WA+MLE$掉成样例分。。

线段树合并太长时间不打了,细节不清,答案在最后统一记导致合并时只能新开节点。

玫瑰花精的印象不深,原来打过一样的$pushup$,要特判儿子是否为空,但这次又忘了。这算挂$91pts$吗?

$code:$

2021.8.8考试总结[NOIP模拟33]_#define2021.8.8考试总结[NOIP模拟33]_c++_02
 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 const int NN=1e5+5;
 4 int n,m,q,to[NN<<1],nex[NN<<1],head[NN<<1],num,ans[NN];
 5 inline int read(){
 6     int x=0,f=1; char ch=getchar();
 7     while(ch<'0'||ch>'9'){ if(ch=='-') f=-1; ch=getchar(); }
 8     while(ch>='0'&&ch<='9'){ x=(x<<1)+(x<<3)+(ch^48); ch=getchar(); }
 9     return x*f;
10 }
11 inline void write(int x){
12     char ch[20]; int len=0;
13     if(x<0) putchar('-'), x=~x+1;
14     do{
15         ch[len++]=x%10+(1<<5)+(1<<4);
16         x/=10;
17     }while(x);
18     for(int i=len-1;i>=0;--i) putchar(ch[i]);
19     return;
20 }
21 inline void add(int a,int b){
22     to[++num]=b; nex[num]=head[a]; head[a]=num;
23     to[++num]=a; nex[num]=head[b]; head[b]=num;
24 }
25 struct segment_tree{
26     int tot,root[NN],mx[NN*40],lc[NN*40],rc[NN*40],lz[NN*40],rz[NN*40];
27     void pushup(int rt){
28         if(!lz[lc[rt]]){ lz[rt]=lz[rc[rt]]; rz[rt]=rz[rc[rt]]; mx[rt]=mx[rc[rt]]; return; }
29         if(!lz[rc[rt]]){ lz[rt]=lz[lc[rt]]; rz[rt]=rz[lc[rt]]; mx[rt]=mx[lc[rt]]; return; }
30         int tmp=lz[rc[rt]]-rz[lc[rt]]-1;
31         mx[rt]=max(max(mx[lc[rt]],mx[rc[rt]]),tmp);
32         lz[rt]=lz[lc[rt]]?lz[lc[rt]]:lz[rc[rt]];
33         rz[rt]=rz[rc[rt]]?rz[rc[rt]]:rz[lc[rt]];
34     }
35     void update(int &rt,int l,int r,int pos){
36         if(!rt) rt=++tot;
37         if(l==r){ lz[rt]=rz[rt]=l; return; }
38         int mid=l+r>>1;
39         if(pos<=mid) update(lc[rt],l,mid,pos);
40         else update(rc[rt],mid+1,r,pos);
41         pushup(rt);
42     }
43     void merge(int &rt,int oth,int l,int r){
44         if(!rt){ rt=oth; return; }
45         if(l==r){
46             if(lz[oth]){
47                 mx[rt]=0;
48                 lz[rt]=rz[rt]=l;
49             }
50             return;
51         }
52         int mid=l+r>>1;
53         if(lz[lc[oth]]) merge(lc[rt],lc[oth],l,mid);
54         if(rz[rc[oth]]) merge(rc[rt],rc[oth],mid+1,r);
55         pushup(rt);
56     }
57 }s;
58 void dfs(int f,int st){
59     for(int i=head[st];i;i=nex[i]){
60         int v=to[i];
61         if(v==f) continue;
62         dfs(st,v);
63         s.merge(s.root[st],s.root[v],1,m);
64     }
65     int now=s.root[st];
66     if(!s.lz[now])ans[st]=-1;
67     else ans[st]=max(s.mx[now],m+s.lz[now]-1-s.rz[now]);
68 }
69 signed main(){
70     n=read(); m=read(); q=read();
71     for(int i=1;i<n;i++) add(read(),read());
72     for(int i=1;i<=q;i++){
73         int u=read(),v=read();
74         s.update(s.root[u],1,m,v);
75     }
76     dfs(0,1);
77     for(int i=1;i<=n;i++)
78         write(ans[i]), putchar('\n');
79     return 0;
80 }
T2

 

T3 Connect

状压全是神仙题。

题意稍微转化一下,不在$1\to n$链上的联通块与链上的点最多只能连一条边。

然后是永远想不到的状态定义:记$f_{s,i}$表示考虑点集为$s$,当前链的终点为$i$的最大留边边权。

有两种转移方式:一是在链的结尾新增一个单点作为新的结尾;二是在链的基础上加一个点集。

预处理出每个点集中的总边权和每个点集向某个单点连边的总边权进行转移。

巧妙的枚举状态$i$补集的子集的方法:

1 int anti=U^i;
2 for(int k=anti;k;k=(k-1)&anti)

$code:$

2021.8.8考试总结[NOIP模拟33]_#define2021.8.8考试总结[NOIP模拟33]_c++_02
 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 const int NN=20;
 4 int n,m,w[NN][NN],f[1<<NN][NN],U,sum[1<<NN],link[1<<NN][NN];
 5 inline int read(){
 6     int x=0,f=1; char ch=getchar();
 7     while(ch<'0'||ch>'9'){ if(ch=='-') f=-1; ch=getchar(); }
 8     while(ch>='0'&&ch<='9'){ x=(x<<1)+(x<<3)+(ch^48); ch=getchar(); }
 9     return x*f;
10 }
11 inline void write(int x){
12     char ch[20]; int len=0;
13     if(x<0){ putchar('-'); x=~x+1; }
14     do{ ch[len++]=x%10+(1<<5)+(1<<4); x/=10; }while(x);
15     for(int i=len-1;i>=0;i--) putchar(ch[i]);
16 }
17 signed main(){
18     n=read(); m=read(); U=1<<n; U--;
19     for(int i=1;i<=m;i++){
20         int a=read(),b=read(),c=read();
21         w[a-1][b-1]=w[b-1][a-1]=c;
22     }
23     for(int i=1;i<=U;i++) 
24         for(int j=0;j<n;j++) for(int k=0;k<j;k++)
25             if((i&(1<<j))&&(i&(1<<k))) sum[i]+=w[j][k];
26     for(int k=0;k<n;k++)
27         for(int i=1;i<=U;i++) for(int j=0;j<n;j++)
28             if(i&(1<<j)) link[i][k]+=w[j][k];
29     memset(f,-1,sizeof(f)); f[1][0]=0;
30     for(int i=1;i<=U;i++) for(int j=0;j<n;j++){
31         if(f[i][j]==-1) continue;
32         for(int k=0;k<n;k++) 
33             if(w[j][k]&&!(i&(1<<k))) f[i|(1<<k)][k]=max(f[i|(1<<k)][k],f[i][j]+w[j][k]);
34         int anti=U^i;
35         for(int k=anti;k;k=(k-1)&anti)
36             f[i|k][j]=max(f[i|k][j],f[i][j]+sum[k]+link[k][j]);
37     }
38     write(sum[U]-f[U][n-1]); putchar('\n');
39     return 0;
40 }
T3