• [考试反思]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
  • 相关阅读:
    IP地址加时间戳加3位随机数
    你会想造一艘船吗?
    提问的智慧
    建造者模式
    设计模式(一)
    jeesite中activiti中的流程表梳理
    如何读书、学习?
    zxing生成高容错率二维码,以及添加文字
    LVM磁盘划分
    阿里云盘扩容(SUSE Linux下)
  • 原文地址:https://www.cnblogs.com/hzoi-DeepinC/p/12329051.html
Copyright © 2020-2023  润新知