• [考试反思]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
  • 相关阅读:
    yocto/bitbake 学习资源
    QEMU/KVM学习资源
    ubuntu 中创建和删除用户
    git 重命名本地和远程分支
    Ubuntu 上搭建 FTP 服务器
    gdb 常见用法
    git log 显示与特定文件相关的 commit 信息
    基于 qemu system mode 运行 arm 程序
    基于 qemu user mode 运行 aarch64 程序
    checking in(airport)
  • 原文地址:https://www.cnblogs.com/hzoi-DeepinC/p/12329051.html
Copyright © 2020-2023  润新知