【2018.10.29】教科书般的亵渎 / 寻宝游戏 / 乐谱分段

题目

WZJ的pdf题解

T1

看过数据,发现只要检查好恶心的暴力就完了。

题意就是让你枚举一种攻击方案使所有人的血量的最小值为 $0space orspace 1$ 且连续(可以有人血量相同)。

枚举一下目标血量序列的最小值是 $0$ 还是 $1$,再枚举一下最大值,然后 $dfs$ 判断是否存在合法情况即可。

不过我数组开小了(正好开的 $7$),输入 $b[7]$ 的时候指针爆到了 $m$ 处,然后把输入值刷给 $m$ 了……调了半天。

 1 #include<bits/stdc++.h>
 2 #define N 8
 3 #define M 8
 4 using namespace std;
 5 inline int read(){
 6     int x=0; bool f=1; char c=getchar();
 7     for(;!isdigit(c);c=getchar()) if(c=='-') f=0;
 8     for(; isdigit(c);c=getchar()) x=(x<<3)+(x<<1)+(c^'0');
 9     if(f) return x;
10     return 0-x;
11 }
12 int n,m,a1[N],a2[N],b1[M],b2[M];
13 int i,j;
14 short vis[13];
15 bool dfs(int cur,int cnt,int num){
16     if(cur>m){
17         if(cnt==j-i+1 && num==n+m) return 1;
18         else return 0;
19     }
20     if(!a2[cur]){
21         if(dfs(cur+1,cnt,num)) return 1;
22         else return 0;
23     }
24     int hp,tmpcnt=cnt,tmpnum=num,tmp2,tmp1;
25     for(int x=1;x<=n;++x){
26         if(!b1[x]) continue;
27         
28         tmp1=max(b1[x]-a2[cur],0);
29         if(tmp1<i) continue;
30         if(--vis[b1[x]]==0 && b1[x]<=j) --cnt;
31         swap(b1[x],tmp1);
32         if(vis[b1[x]]++==0 && b1[x]<=j) ++cnt;
33         if(tmp1>j && b1[x]<=j) ++num;
34         
35         tmp2=max(b2[cur]-a1[x],0);
36         if(tmp2<i) continue;
37         if(--vis[b2[cur]]==0 && b2[cur]<=j) --cnt;
38         swap(b2[cur],tmp2);
39         if(vis[b2[cur]]++==0 && b2[cur]<=j) ++cnt;
40         if(tmp2>j && b2[cur]<=j) ++num;
41         
42         //printf("dfs:%d->%d %d %d  %d->%d %d->%d
",cur,x,cnt,num,tmp1,b1[x],tmp2,b2[cur]); 
43         if(dfs(cur+1,cnt,num)) return 1;
44         
45         num=tmpnum;
46         cnt=tmpcnt;
47         --vis[b2[cur]];
48         swap(b2[cur],tmp2);
49         ++vis[b2[cur]];
50         --vis[b1[x]];
51         swap(b1[x],tmp1);
52         ++vis[b1[x]];
53     }
54     if(dfs(cur+1,cnt,num)) return 1;
55     return 0;
56 }
57 int main(){
58     //freopen("defile.in","r",stdin);
59     //freopen("defile.out","w",stdout);
60     n=read(),m=read();
61     int to=0;
62     for(i=1;i<=n;++i) a1[i]=read(),b1[i]=read(),to=max(to,b1[i]),vis[b1[i]]++;
63     for(i=1;i<=m;++i) a2[i]=read(),b2[i]=read(),to=max(to,b2[i]),vis[b2[i]]++;
64     for(i=0;i<=1;++i){
65         int lj=0,_lj=0;
66         for(j=i;j<=min(i+n+m-1,to);++j){
67             if(vis[j]) ++lj;//printf("%d %d %d %d %d
",i,j,lj,_lj+vis[j],vis[2]);
68             if(dfs(1,lj,_lj+=vis[j])){printf("Yes
"); return 0;}//system("pause");
69         }
70     }
71     printf("No
");
72     return 0;
73 }
74 /*
75 4 3
76 1 8 6 5 4 1 3 8
77 4 2 7 2 8 6
78 */
View Code

T2

$noip2017space D2T2$ 再现

什么,这状压裸题还用讲嘛?

一看 $k=15$,以每个宝藏点为起点 $bfs$ 求一下它到其它各个宝藏点的最短距离,然后设 $dp(s,i)$ 表示宝藏点挖掘情况的 $01$ 集合为 $s$,当前最后挖到第 $i$ 个宝藏点时所需的最短时间。若一个情况被转移后总用时$le T$ ,就再更新一下能在规定时间 $T$ 内挖到的最大宝藏数量。这个计算嘛,我用的就是 $noip2018$ 提高组初赛 $T10$ 的做法,直接计算挖掘情况 $s$ 中 $1$ 的数量,就是挖的宝藏数量。

时间复杂度 $O(2^k*k^2)$。

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 inline int read(){
 4     int x=0; bool f=1; char c=getchar();
 5     for(;!isdigit(c);c=getchar()) if(c=='-') f=0;
 6     for(; isdigit(c);c=getchar()) x=(x<<3)+(x<<1)+(c^'0');
 7     if(f) return x;
 8     return 0-x;
 9 }
10 
11 const int fx[4][2]={{0,1},{1,0},{0,-1},{-1,0}};
12 int n,m,k,T,dis[502][502],dp[1<<16][16],far[16][16];
13 char c[502][502];
14 vector<pair<int,int> >v;
15 bool vis[502][502];
16 void bfs(int s){
17     queue<pair<int,int> >Q;
18     Q.push(v[s]);
19     memset(dis,0x7f,sizeof dis);
20     memset(vis,0,sizeof vis);
21     dis[v[s].first][v[s].second]=0;
22     vis[v[s].first][v[s].second]=1;
23     int i,x,y,gx,gy;
24     while(!Q.empty()){
25         x=Q.front().first, y=Q.front().second, Q.pop();
26         for(i=0;i^4;++i){
27             gx=x+fx[i][0], gy=y+fx[i][1];
28             if(gx<1 || gy<1 || gx>n || gy>m || c[gx][gy]=='#' || vis[gx][gy]) continue;
29             dis[gx][gy]=dis[x][y]+1, vis[gx][gy]=1;
30             Q.push(make_pair(gx,gy));
31         }
32     }
33 }
34 
35 int ans;
36 int main(){
37     //freopen("treasure.in","r",stdin);
38     //freopen("treasure.out","w",stdout);
39     n=read(),m=read(),k=read(),T=read();
40     int i,j,p;
41     v.push_back(make_pair(0,0));
42     for(i=1;i<=n;++i){
43         scanf("%s",c[i]+1);
44         for(j=1;j<=m;++j){
45             if(c[i][j]=='*') v.push_back(make_pair(i,j));
46             if(c[i][j]=='S') v[0]=make_pair(i,j);
47         }
48     }
49     for(i=0;i<=k;++i){
50         bfs(i);
51         for(j=1;j<=k;++j) far[i][j]=dis[v[j].first][v[j].second];
52     }
53     int to=1<<k,go;
54     memset(dp,0x7f,sizeof dp);
55     for(i=1,go=1; i<=k; ++i, go<<=1){
56         dp[go][i]=far[0][i];
57         if(dp[go][i]<=T) ans=1;
58     }
59     for(i=1;i^to;++i){
60         for(j=1;j<=k;++j){
61             if(!(i & (1<<(j-1)))){
62                 go=i|(1<<(j-1));
63                 for(p=1;p<=k;++p) if(i & (1<<(p-1))) dp[go][j]=min(dp[go][j],dp[i][p]+far[j][p]);
64                     //printf("(%d,%d)<-(%d,%d) %d %d %d
",go,j,i,p,dp[go][j],dp[i][p],far[j][p]);
65                 if(dp[go][j]<=T){
66                     int tmp=go,zero=0;
67                     while(tmp) tmp&=(tmp-1), ++zero;
68                     ans=max(ans,zero);
69                     //printf("newans:%d %d
",go,zero);
70                 }
71             }
72         }
73         //system("pause");
74     }
75     /*
76     for(i=0;i^to;++i){
77         for(j=1;j<=k;++j)
78             if(!(i & (1<<(j-1)))) printf("%d ",dp[i|(1<<(j-1))][j]);
79         putchar('
');
80     }*/
81     printf("%d
",ans);
82     return 0;
83 }
84 /*
85 4 5 6 9
86 **...
87 *##*.
88 #**..
89 ..S..
90 */
View Code

T3

神仙省选级题目,我感觉我要被集训队吊着打……

20pts:

瞎 jb 搜。

60pts:

设 $f_i$ 表示前 $i$ 个音符已经划分完毕,已经划分过的最短乐章最长是多少。

易得 $f_i = max{min(f_j, i − j)}$,条件是 $[j + 1, i]$ 区间没有相同的音符。

计算方案的话可以设 $g_i$ 表示前 $i$ 个音符已经划分完毕,已经划分过的乐章长度均 $ge f_n$ ,可得 $g_i =sum_{j=0}^{i−f_n} g_j$,其中 $[j + 1, i]$ 区间没有相同的音符,条件是 $j$ 为最大的满足要求的。

至于求没有相同音符的区间 $[j + 1, i]$ ,一直向前扩展到出现一个重复字符为止就可以了,中间的 $j$ 都满足要求。

答案即为 $f_n$ 和 $g_n$ ,时间复杂度为 $O(n^2)$。

100pts:

仔细思考,会想到求没有相同音符的区间 $[j + 1, i]$ 这个东西,可以用滑动窗口直接求最大的需要转移的 $j$,因为 $i$ 往右移动,$j$ 也会往右移动或不动。

而快速取到最大 $j$ 的方法搞定后,$g$ 数组就只是简单地转移了前缀和。也就是说可以 $O(n)$ 推 $g$ 数组。

但是 $f$ 数组的递推没法优化到 $O(n^2)$ 以下,因此不能再推它了。

现在的问题成了如何直接得知 乐章长度下限 $f_n$。

我们注意到可以通过二分卡下界($mid$ 设为最短乐章长度的最小值)的方式来求出 $f_n$。

具体原因是,由于这种二分只卡下界,$f_n$ 具有单调性。如果 $f_n$ 取某一值可以,那取更大的值也可以。

于是 $g_i=sum_{j=0}^{i-mid} g_j$。

判断是否存在让每段乐章长度均$ge mid$ 的方案,直接判 $g_n$ 是否大于 $0$ 即可。

时间复杂度 $O(n*log(n))$。

 1 #include<cstdio>
 2 #include<cstring>
 3 #include<cctype>
 4 #include<cmath>
 5 #include<algorithm>
 6 #define rep(i,s,t) for(int i=s;i<=t;i++)
 7 #define dwn(i,s,t) for(int i=s;i>=t;i--)
 8 using namespace std;
 9 inline int read() {
10     int x=0,f=1;char c=getchar();
11     for(;!isdigit(c);c=getchar()) if(c=='-') f=-1;
12     for(;isdigit(c);c=getchar()) x=x*10+c-'0';
13     return x*f;
14 }
15 typedef long long ll;
16 const int maxn=530010;
17 const int mod=998244353;
18 int n,A[maxn],f[maxn],st[maxn],last[maxn],tmp[maxn],g[maxn],sumv[maxn];
19 int check(int lim) {
20     sumv[0]=1;
21     rep(i,1,n) {
22         int l=st[i]-1,r=i-lim;
23         if(l>r) f[i]=0;
24         else f[i]=(sumv[r]-(l>=1?sumv[l-1]:0))?1:0;
25         sumv[i]=sumv[i-1]+f[i];
26     }
27     return f[n];
28 }
29 int calc(int ans) {
30     g[0]=sumv[0]=1;
31     rep(i,1,n) {
32         int l=st[i]-1,r=i-ans;
33         if(l>r) g[i]=0;
34         else g[i]=(sumv[r]-(l>=1?sumv[l-1]:0)+mod)%mod;
35         sumv[i]=(sumv[i-1]+g[i])%mod;
36     }
37     return g[n];
38 }
39 int main() {
40     //freopen("instrument.in","r",stdin);
41     //freopen("instrument.out","w",stdout);
42     n=read();
43     rep(i,1,n) tmp[i]=A[i]=read();
44     sort(tmp+1,tmp+n+1);
45     rep(i,1,n) A[i]=lower_bound(tmp+1,tmp+n+1,A[i])-tmp;
46     rep(i,1,n) st[i]=max(st[i-1],last[A[i]]+1),last[A[i]]=i;
47     int l=1,r=n+1,mid;
48     while(l+1<r) if(check(mid=l+r>>1)) l=mid; else r=mid;
49     printf("%d
%d
",l,calc(l));
50     return 0;
51 }
View Code
原文地址:https://www.cnblogs.com/scx2015noip-as-php/p/2018_10_29.html