[考试反思]0218省选模拟25:游离

还不错的一场。。。每次到我这里都是一个断档,往上差好多分,往下差不了多少

不管怎么说,难得排名稍微好一次。。。

$T2$猜的结论伪了,然后$BFS$还写锅了(萌新刚学OI并不觉得丢人)

其实猜的差不多,稍微弄一弄把$BFS$改一下就$50$了

$T3$留的时间不多然后写$30$的部分分挂了$10$分。结果是$vis$数组忘用了。。。弱智错误还是很多,但是凭借着$T1$乱写$A$了所以苟住了。

然而与「我考好」这件事一样反常,这次不是改题大神了。改题巨慢。。。

因为$T3$差不多是$dy$讲的原题然而当时没有听懂所以今天研究了好长时间。

脑子好慢啊。。。学新东西慢这是个大毛病啊。。。

 

T1:环

大意:要求构造一个$01$序列,长为$n$且恰好有$k$个$1$。满足其中一个$1$与其右侧的$0$交换后与原序列循环同构。$n \le 100,T \le 10$

如果$gcd(n,k)$不是$1$那么一定无解。证明很简单,用$1$的下标和算一下。

循环右移下标和增加$kx$。$x$是位移位数。而交换操作会使下标增加$1$。则$kx \equiv 1 (mod \ n)$

顺便还是讲一下我的弱智思路。

我们发现这些$1$中间有若干数量的$0$。我们称之为零段。一共有$k$个。

如果最短的零段长度为$x$那么最长的零段长度一定不会超过$x+1$。否则交换一次不可能仍然循环同构。

然后我们把这些零段固有的$k$都删去,这个新的零段长度序列也就是一个$01$序列了。

我们发现每次交换操作是把一个$1$与左边的$0$交换,而且也要求循环同构。与原来的问题很像$reverse$一下就好了。

所以我们要构造一个新的满足条件的序列,长度为$k$,里面$1$的个数是$n \% k$。

可以递归求解了。和辗转相除是一样的复杂度有个$log$。

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 int s[111][111],t,l,k,n,res[111],dif[111],ans;
 4 int gcd(int x,int y){return y?gcd(y,x%y):x;}
 5 void solve(int n,int k,int f){
 6     if(k==1){for(int i=2;i<=n;++i)s[f][i]=0;s[f][1]=1;return;}
 7     int a=n/k,e=n%k,c=0;
 8     solve(k,e,f+1);
 9     reverse(s[f+1]+1,s[f+1]+k+1);
10     for(int i=1;i<=k;++i){
11         s[f][++c]=1;
12         for(int j=1;j<a+s[f+1][i];++j)s[f][++c]=0;
13     }
14 }
15 bool chk(int st){
16     int x=0;
17     for(int i=st;i<=n;++i)res[++x]=s[0][i];
18     for(int i=1;i<st;++i)res[++x]=s[0][i];
19     for(int i=1;i<=n;++i)dif[i]=res[i]-s[0][i];
20     int c=0;
21     for(int i=1;i<=n;++i)c+=dif[i]?1:0;
22     if(c!=2)return 0;
23     for(int i=1;i<=n;++i)if(dif[i]==-1&&dif[i%n+1]==1)return 1;
24     return 0;
25 }
26 int main(){
27     cin>>t;while(t-->0){
28         cin>>n>>k>>l;
29         if(gcd(n,k)!=1) {puts("NO");continue;}
30         puts("YES");
31         solve(n,k,0);
32         for(int i=1;i<=n;++i)printf("%d",s[0][i]);puts("");
33         for(int i=1;i<=n;++i)if(chk(i))ans=i;
34         int bg=1;
35         for(int i=2;i<=l;++i){
36             bg=(bg+ans-2)%n+1;
37             for(int j=bg;j<=n;++j)printf("%d",s[0][j]);
38             for(int j=1;j<bg;++j)printf("%d",s[0][j]);
39             puts("");
40         }
41     }
42 }
View Code

题解做法相较于这个不知道高明到哪里去了。。。

求个逆元,然后把数填在$\frac{0,1,...,k-1}{k}$上。循环位移$\frac{1}{k}$位就好。交换$0,1$就好。

就没了。代码也简单得多。。神仙想到的

 

T2:DNA序列

大意:给定$n$个字符串,要求你把它们的非空前缀任意次序拼接后字典序最小。$n,maxlen \le 50$

最简单粗暴的是把所有字符串按照字典序排序,显然是错的。

因为后半截我们不要。。。所以单纯按照字典序排序不对劲。。。

我们发现,如果有几个字符串它们的前缀分别含有字符串$x,z$。而其中前缀为$x$中的字符串的结构如果说是$xxxxy$

那么我们会把这个字符串放在所有前缀为$x$中的最后一个,因为你把所有$x$都放出去之后由于$y<z$所以你会把$y$弄得靠前一些。

这样我们发现一个字符串只要分成两部分就好了,一个是重复若干遍的前缀,一个是剩余部分。

为了方便处理我们给每个字符串后面加上一个极大字符。

我们找到每个串的最短前缀满足$xxx...xx<S$。并提取出剩余部分。

然后就可以按照前缀为第一关键字升序,剩余部分为第二关键字降序排序。

这样我们就知道截前缀的顺序了。

然后应该怎么截。

大神们都写的贪心,我觉得他们特别巨:

排序后倒序考虑所有字符串,每次截取一个前缀当作加入当前答案的前缀更新答案。暴力枚举长度即可。

时间复杂度是$O(n^4)$的

然而像我这种比较笨的嘛。。。当然只能想到$BFS$啦

状态是三维的,$X,Y,L$表示目前填了第$X$串的第$Y$个字符在答案串的$L$位置上。

因为字符串最优字典序平时都是逐位考虑的,所以采用$BFS$。逐位更新。

对于当前的状态前方的串一定与当前的答案串完全契合,如果大了就不会更新答案剪枝,如果小了就直接更新答案

(显然只会更新一位因为$BFS$队列里的元素$L$值至多相差$1$)

也停简单粗暴的。大神都不这么写。。。

因为每次只会更新一位所以状态数与时间复杂度同级。时间复杂度也是$O(n^4)$的

这题对于不会熟练使用$string$的人代码真的是又丑又长。。

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 string s[55];int len[55],n,anslen,qx[33333333],qy[33333333],ql[33333333],al[55][55][2555];char ans[3333];
 4 struct stf{
 5     string x,r,c;
 6     friend bool operator<(stf a,stf b){return a.c<b.c||(a.c==b.c&&a.r>b.r);}
 7 }ss[55];
 8 int main(){
 9     cin>>n;
10     for(int i=1;i<=n;++i)cin>>ss[i].x,ss[i].x+='Z';
11     for(int i=1;i<=n;++i){
12         int len=ss[i].x.length();
13         for(int j=1;j<len;++j){
14             int cnt=0;
15             for(int k=0;k<len;++k)if(ss[i].x[k]<ss[i].x[k%j])goto E;else if(ss[i].x[k]>ss[i].x[k%j])break;
16             for(int k=0;k<len;++k)if(ss[i].x[k]==ss[i].x[k%j])cnt++;else break;
17             cnt/=j;
18             for(int k=0;k<j;++k)ss[i].c+=ss[i].x[k];
19             for(int k=cnt*j;k<len;++k)ss[i].r+=ss[i].x[k];
20             ss[i].c+='Z';
21             break;E:;
22         }
23     }
24     sort(ss+1,ss+1+n);
25     for(int i=1;i<=n;++i)s[i]=ss[i].x,len[i]=s[i].length()-1;
26     s[n+1]+='A'-1;len[n+1]=1;
27     for(int i=1;i<2888;++i)ans[i]='Z'-1;
28     qx[1]=1;qy[1]=0;ql[1]=1;
29     for(int h=1,t=1;h<=t;++h){
30         int X=qx[h],Y=qy[h],L=ql[h];
31         if(al[X][Y][L])continue;else al[X][Y][L]=1;
32         if(s[X][Y]<ans[L]){while(ql[t]==L+1)t--;ans[L]=s[X][Y];}
33         if(s[X][Y]==ans[L])if(X!=n+1){
34             if(Y!=len[X]-1&&s[X][Y+1]<=ans[L+1])qx[++t]=X,qy[t]=Y+1,ql[t]=L+1,ans[L+1]=min(ans[L+1],s[X][Y+1]);
35             if(s[X+1][0]<=ans[L+1])qx[++t]=X+1,qy[t]=0,ql[t]=L+1,ans[L+1]=min(ans[L+1],s[X+1][0]);
36         }
37     }for(int i=1;ans[i]!='A'-1;++i)putchar(ans[i]);
38 }
View Code

 

T3:探寻

大意:有根树,每个边都有代价和收益(先代价后收益)。有一个点为目标点,求从根出发到达目标点的最小初始金钱(可多次随时折返而只付出一次代价)。$n \le 200000$

首先稍微操作一下,把目标点的收益改为$inf$。答案就是遍历整棵树的最小初始代价。

如果问题放在序列上,那就是已经做烂了的了。对于所有净收益非负的点按照代价由小到大贪心的吃就好了。

树上的依赖关系,我们考虑用一种类似于延迟标记的方法来解决。(不知道这么说是否合适)

我们依旧把所有点按照上述方法排序,但是这次是动态的所以转而用堆维护($set$平衡树也可以)

考虑一个点现在作为堆顶代价最小会出现什么情况:

1,它的父亲到根的路径上的所有点都已经被经历过了,那么这个点你就可以直接买下来了。和序列的做法一样。

2,如果它到根的路径上的点并没有被经历(经历是指在堆中被取出考虑过),如果往上跳到第一个没有被经历过的点为$fa$。

  那么就说明经历$fa$的代价比经历当前点要大且当前点还有净收益。只要$fa$一旦被经历你就会立刻经历这个点,因为可以赚钱。

  也就是现在这个点和父亲可以认为是一个套餐,绑定起来了。所以就尝试把这个点合并到$fa$中去考虑。

  (1)如果$cost_p > score_{fa}$那么就是说你到达这个点单用父亲的收益还不够还要从外界拿。

    那么更新$cost_{fa}=cost_{fa}+cost_p -score_{fa}$,而父亲收益被花掉了而新节点有新收益$score_fa=score_p$

  (2)否则,到达父亲就可以直接用在父亲处赚的钱来经历儿子。$score_{fa}=score_{fa}+score_p -cost_p$

这样就可以把当前节点抹掉,更新父节点然后继续考虑了。

找$fa$嘛。。。直接并查集维护就完事了

然后做法就和序列上的差不多了。代码极度好写。。。时间复杂度$O(n \ log \ n)$

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 #define S 233333
 4 #define ll long long
 5 int n,f[S],al[S],g[S];long long ans,lft,e[S],c[S];
 6 struct P{
 7     ll e,c;int p;
 8     friend bool operator<(P x,P y){return x.e>x.c^y.e>y.c?x.e>x.c:(x.c^y.c?x.c<y.c:x.p<y.p);}
 9 };set<P>s;
10 int find(int p){return g[p]==p?p:g[p]=find(g[p]);}
11 int main(){
12     cin>>n;al[0]=1;
13     for(int i=1;i<n;++i)scanf("%d%lld%lld",&f[i],&e[i],&c[i]),e[i]=e[i]==-1?1e16:e[i],s.insert((P){e[i],c[i],i}),g[i]=i;
14     while(!s.empty()){
15         P x=*s.begin();s.erase(s.begin());
16         al[x.p]=1;g[x.p]=f[x.p];
17         if(!al[find(x.p)]){int F=find(x.p);
18             s.erase(s.find((P){e[F],c[F],F}));if(e[F]<x.c)c[F]+=x.c-e[F];
19             s.insert((P){e[F]>=x.c?(e[F]+=x.e-x.c):e[F]=x.e,c[F],F});
20         }else{if(lft<x.c)ans+=x.c-lft,lft=x.c;lft+=x.e-x.c;}
21     }cout<<ans<<endl;
22 }
View Code

 

posted @ 2020-02-18 22:10  DeepinC  阅读(190)  评论(0编辑  收藏  举报