2021.8.4考试总结[NOIP模拟30]

T1 毛衣衬


将合法子集分为两个和相等的集合。

暴力枚举每个元素是否被选,放在哪种集合,复杂度$O(3^n)$。考虑$\textit{meet in the middle}$。

将全集等分分为两部分分别考虑,先$O(3^{\frac{n}{2}})$枚举前一部分的所有情况,记录两个集合的差所对应的状态,然后同样$O(3^{\frac{n}{2}})$枚举后一部分,与前一部分进行匹配即可。

$\textit{meet in the middle}$真还挺神的,以后做题要多考虑。

$code:$

 1 #include<bits/stdc++.h>
 2 #define int long long
 3 #define rin register signed
 4 #define pb push_back
 5 using namespace std;
 6 const int NN=25;
 7 int n,m[NN],tot,mx,mid,ans,cnt;
 8 bool vis[10000005];
 9 map<int,int>tmp;
10 vector<int>sta[10000005];
11 inline int read(){
12     int x=0,f=1; char ch=getchar();
13     while(ch<'0'||ch>'9'){ if(ch=='-') f=-1; ch=getchar(); }
14     while(ch<='9'&&ch>='0'){ x=(x<<1)+(x<<3)+(ch^48); ch=getchar(); }
15     return x*f;
16 }
17 inline void write(int x){
18     char ch[20]; int len=0;
19     if(x<0) x=~x+1, putchar('-');
20     do{
21         ch[len++]=x%10+(1<<5)+(1<<4);
22         x/=10;
23     }while(x);
24     for(rin i=len-1;i>=0;--i) putchar(ch[i]);
25 }
26 void ldfs(int s,int ls,int rs,int st){
27     if(s>mid){ 
28         if(!tmp[ls-rs]) tmp[ls-rs]=++cnt;
29         int now=tmp[ls-rs];
30         sta[now].pb(st); 
31         return; 
32     }
33     ldfs(s+1,ls,rs,st);
34     ldfs(s+1,ls+m[s],rs,st|(1<<(s-1)));
35     ldfs(s+1,ls,rs+m[s],st|(1<<(s-1)));
36 }
37 void rdfs(int s,int ls,int rs,int st){
38     if(s>n){
39         if(tmp.find(rs-ls)==tmp.end()) return;
40         int now=tmp[rs-ls];
41         for(int i=0;i<sta[now].size();i++){
42             if(st&sta[now][i]) continue;
43             if(!(st|sta[now][i])) continue;
44             if(!vis[st|sta[now][i]]) vis[st|sta[now][i]]=1, ++ans;
45         }
46         return;
47     }
48     rdfs(s+1,ls,rs,st);
49     rdfs(s+1,ls+m[s],rs,st|(1<<(s-1)));
50     rdfs(s+1,ls,rs+m[s],st|(1<<(s-1)));
51 }
52 signed main(){
53     n=read(); tot=(1<<n)-1; mid=n>>1;
54     for(rin i=1;i<=n;i++) m[i]=read();
55     ldfs(1,0,0,0); rdfs(mid+1,0,0,0);
56     write(ans); putchar('\n');
57     return 0;
58 }
T1

T2 猫儿沉


每个位置只能互换一次,那么可以通过当前位置的值与它的位置的大小关系得到一些互换的顺序。

令$pos_i$为元素$a_i$在序列中的位置下标,即$pos_{a_i}=i$,对于序列$s$变为序列$p$的操作顺序,根据大小关系分类讨论:

$pos_i=i$时一定无解,因为这个元素一定要被换到别的地方;

$pos_i<i$时要保证$i$往左移,那么:

为了防止$i$被换到右边,$i-1$位要早于$i$位;

为了使$i$一直被向左换,$i-k$位要早于$i-k-1$位;

为了防止$i$被换到$pos_i$左边,$pos_i-1$位要早于$pos_i$位。

$pos_i>i$时类似。预处理出每个位置的互换顺序,如果过程中有相矛盾的地方,那么无解(数据里没有无解的情况,我懒得没判。

接下来状态转移。定义$f_{i,j}$表示考虑到第$i$个元素,且第$i$个元素在已有序列中拓扑序为$j$的方案数,$tag_i$等于$1$表示第$i-1$位互换早于第$i$位;等于$2$表示第$i$位互换早于第$i-1$位。

于是有应该很好理解的转移方程:

$f_{i,j}=\begin{cases}\sum_{k=1}^{j}f_{i-1,k} & tag_i=1\\\sum_{k=i+1}^{i}f_{i-1,k} & tag_i=2
\end{cases}$

前缀和优化到$O(n^2)$。

$code:$

 1 #include<bits/stdc++.h>
 2 #define rin register signed
 3 using namespace std;
 4 const int p=1e9+7,NN=5e3+5;
 5 int n,a[NN],tag[NN],pos[NN];//tag==1:i-1在i之前;2:i-1在i之后
 6 int f[NN][NN],pre[NN][NN];
 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<='9'&&ch>='0'){ 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) x=~x+1, putchar('-');
16     do{
17         ch[len++]=x%10+(1<<5)+(1<<4);
18         x/=10;
19     }while(x);
20     for(int i=len-1;i>=0;--i) putchar(ch[i]);
21 }
22 signed main(){
23     n=read();
24     for(rin i=1;i<=n;i++) a[i]=read()+1, pos[a[i]]=i;
25     for(rin i=1;i<=n;i++){
26         if(pos[i]==i){ puts("0"); return 0; }
27         if(pos[i]<i){
28             tag[i]=1; tag[pos[i]]=1;
29             for(rin j=pos[i]+1;j<i;j++) tag[j]=2;
30         }
31         else{
32             tag[i]=2; tag[pos[i]]=2;
33             for(rin j=i+1;j<a[i];j++) tag[j]=1;
34         }
35     }
36     f[1][1]=pre[1][1]=1;
37     for(rin i=2;i<=n;i++){
38         if(tag[i]==2) for(rin j=1;j<=i;j++) f[i][j]=(pre[i-1][i-1]-pre[i-1][j-1]+p)%p;
39         else for(rin j=1;j<=i;j++) f[i][j]=pre[i-1][j-1]%p;
40         for(rin j=1;j<=i;j++) pre[i][j]=(pre[i][j-1]+f[i][j])%p;
41     }
42     write(pre[n-1][n-1]); putchar('\n');
43     return 0;
44 }
T2

T3 茅散尘


考虑二分。

枚举$x$,在每个$x$下二分背包可以允许的最大容量,最终得到的最小值就是答案。每次check$O(n)$,总复杂度$O(pnlog_{\sum a})$。

显然会T。考虑剪枝。

每次枚举$x$时先$check$一下当前最优答案$ans$,如果连$ans$都不能满足就不用二分,直接跳过即可。在此基础上可以对$x$做一下随机化处理。但我加随机化反而变慢了

$code:$

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 int p,n,k,a[10005],b[10005];
 4 int l,r,ans=INT_MAX;
 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<='9'&&ch>='0'){ 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) x=~x+1, putchar('-');
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 }
20 bool check(int mid){
21     int btmp=0,ttmp=0,res;
22     while(btmp<k&&ttmp<n){
23         btmp++; res=0;
24         while(res+b[ttmp+1]<=mid&&ttmp<n) res+=b[++ttmp];
25     }
26     return ttmp==n;
27 }
28 void getmid(int x){
29     int l=0,r=0,res;
30     for(int i=1;i<=n;i++){
31         b[i]=(a[i]+x)%p; 
32         r+=b[i]; l=max(l,b[i]);
33     }
34     if(!check(ans)) return;
35     while(l<=r){
36         int mid=l+r>>1;
37         if(check(mid)) r=mid-1, res=mid;
38         else l=mid+1;
39     }
40     ans=min(ans,res);
41 }
42 signed main(){
43     n=read(); p=read(); k=read();
44     for(int i=1;i<=n;i++) a[i]=read();
45     for(int i=0;i<p;i++) getmid(i);
46     write(ans); putchar('\n');
47     return 0;
48 }
T3

原(zhen)始(zheng)题名:T1毛一琛;T2毛二琛;T3毛三琛

《毛 一 琛》

posted @ 2021-08-04 17:53  keen_z  阅读(61)  评论(1编辑  收藏  举报