[考试反思]0511省选模拟93:平衡

一套差评比好评多$25$的题。。。出题人尝试通过题目名干扰心态?

然而我昨天一学文化课今天人都傻了。。。打暴力就没什么好说的

$T1$意外的高,$T2,3$都稍微挂了一点。

$T1$打的是一个暴力。虽说是暴力但是貌似的确卡不掉。也有点莫名其妙的成分。。。

$T2$其实已经想到正解,但是少发现了一条性质导致优化空间没了。

像个傻子一样写了个特判把本来没$T$的点给卡$WA$了。$80 \rightarrow 60$

$T3$纯粹是最后几分钟没事干想乱输出点东西,理论能得$15$分然而还写挂了,丢了。

好像状态不是很好。但是歪打正着$A$了一个。混名次跑路吧。。。

 

T1:A(超简单题)

大意:给定小写字母串。$Q$次询问求排名为$k$的子序列的最后$q$个字符。(出现多次算一次)$|S|\le 3 \times 10^5,Q \le 10^5,\sum p \le 10^6,k \le 10^{18}$

首先要搞出所有子序列,弄个自动机。子序列自动机就是倒着扫一遍维护每种字符上一次出现位置。

每个字符向$26$种字符的上一次出现位置连边。可以不重不漏构成所有子序列。

然后我的暴力做法是,离线所有询问并按照$k$排序。然后一起在自动机上跑。

$solve(p,k,l,r)$表示在节点$p$,已有排名为$k$,目前在解决$[l,r]$的询问。

在当前节点解决所有$k==q[i].k$的询问。剩下的枚举$26$出边,二分找到那些询问归属于这条出边,递归解决。

时间复杂度玄学。因为有$k \le 10^{18}$的限制所以不好卡。

希望大神能$hack$掉这个鬼东西。

观察了一下人牛逼大神的代码发现没必要二分,因为执行过程就是从小到大的,所以直接全局记录已经处理了几个询问就行了。

还少个$log$。我真是太菜了。

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 #define ll long long
 4 const ll S=333333,inf=1e18+9;
 5 ll sz[S];int c[S][26],n,lp[26],q;char s[S],R[S];
 6 vector<char>ans[S];
 7 struct Qs{ll k;int p,o;friend bool operator<(Qs x,Qs y){return x.k<y.k;}}Q[S];
 8 void solve(int p,int d,int l,int r,ll k){
 9     if(l>r)return;
10     if(p){
11         k++; R[++d]=s[p];
12         while(Q[l].k==k){
13             for(int j=max(1,d-Q[l].p+1);j<=d;++j)ans[Q[l].o].push_back(R[j]);
14             l++;
15         }
16     }
17     ll v;for(int i=0,x;i<26;++i)if(c[p][i])v=min(k+sz[c[p][i]],inf),x=upper_bound(Q+l,Q+r+1,(Qs){v,0,0})-Q-1,solve(c[p][i],d,l,x,k),k=v,l=x+1;
18 }
19 int main(){
20     scanf("%s",s+1); n=strlen(s+1);
21     for(int i=n;~i;--i){
22         sz[i]=1;
23         for(int j=0;j<26;++j)if(lp[j])c[i][j]=lp[j],sz[i]+=sz[lp[j]],sz[i]=min(sz[i],inf);
24         if(i)lp[s[i]-'a']=i;
25     }scanf("%d",&q);
26     for(int i=1;i<=q;++i)scanf("%lld%d",&Q[i].k,&Q[i].p),Q[i].o=i;
27     sort(Q+1,Q+1+q); Q[q+1].k=inf+1;
28     solve(0,0,1,q,0);
29     for(int i=1;i<=q;++i){
30         if(ans[i].empty())puts("-1");
31         else{for(int j=0;j<ans[i].size();++j)putchar(ans[i][j]);puts("");}
32     }
33 }
View Code

正解的话只要分一下轻重链,每次倍增上二分跳重链即可。代码略难写。时间复杂度$Qlog^2|S|$

 

T2:B(更简单题)

大意:$n$张牌$d$个人。一共进行$\\frac{n}{d}$轮,第$i$轮用前$id$张牌。第$i$张牌有颜色$\in [0,d)$,上面写着数字$i$。

每一轮会进行$m$次:随机一个$x \in [1,id)$,然后交换$x,x \mod d+1$。然后把第$i$张牌分给第$i \mod d$个人。

一个人的积分是一轮中他被分到的 颜色编号和人的编号相同的牌上的数字和。求每个人的$\frac{n}{d}$轮的总积分。

输出浮点数。相对或绝对误差$\le 10^{-5}$ 。$d \le 10,n \le 3 \times 10^6,m \le 10^{11}$

首先因为分配关系是模意义下的所以我们只需要知道每个牌的最终位置$\mod d$的值。

这个东西可以在每一轮用矩阵快速幂处理。发现可以转移矩阵是循环的所以可以优化。

至此复杂度是$O(\frac{n}{d} d^2 log m)=O(ndlog\ m)$

交换次数多的时候一张牌出现在每个位置的概率大约是均等的。阈值大约是$10^6$。这就不用做矩阵快速幂了。

 1 #include<cstdio>
 2 double ans[10],p[10],b[10][10],tot[10][10],r[10];
 3 char s[3000005];
 4 int n,d; long long m;
 5 int mo(int x){return x>=d?x-d:x;}
 6 void mulb(){
 7     for(int i=0;i<d;++i)for(int j=0;j<d;++j)r[j]+=b[0][i]*b[i][j];
 8     for(int i=0;i<d;++i)b[0][i]=r[i],r[i]=0;
 9     for(int i=1;i<d;++i)for(int j=0;j<d;++j)b[i][j]=b[i-1][j?j-1:d-1];
10 }
11 void mulp(){
12     for(int i=0;i<d;++i)for(int j=0;j<d;++j)r[j]+=p[i]*b[i][j];
13     for(int i=0;i<d;++i)p[i]=r[i],r[i]=0;
14 }
15 void calpos(double P){
16     if(n>300000&&m>300000){for(int i=0;i<d;++i)p[i]=1./d;return;}
17     for(int i=0;i<d;++i)p[i]=0; p[0]=1;
18     for(int i=0;i<d;++i)for(int j=0;j<d;++j)b[i][j]=i==j?1-P-P:(mo(i-j+d)==1||mo(j-i+d)==1?P:0);
19     for(long long t=m;t;t>>=1,mulb())if(t&1)mulp();
20 }
21 int main(){
22     scanf("%d%d%lld%s",&n,&d,&m,s);
23     for(int i=0;i<n;i+=d){
24         for(int j=i;j<i+d;++j)tot[s[j]-48][j%d]+=j+1;
25         calpos(1.L/(i+d));
26         for(int j=0;j<d;++j)for(int k=0;k<d;++k)ans[j]+=p[mo(j-k+d)]*tot[j][k];
27     }for(int i=0;i<d;++i)printf("%.9lf\n",ans[i]);
28 }
View Code

 

T3:C(最简单题)

大意:给两棵边权树,它们的叶节点个数相同($1$号点不算叶节点)。你要把两棵树的叶节点两两配对,每对之间连上$inf$边。

然后你要砍掉一些边,使得整个图剩余两个联通块,两棵树的根不在同一个联通块,每个联通块都是一棵树。最小化砍掉的边权和。$n \le 10^5$

设一棵树中叶子节点个数为$m$

首先砍成两棵树就很麻烦,我们把根节点连一下就是一棵树了。

假如我们得到了最终的树的形状,然后我们删除其中的所有$inf$边。那么整个图就分裂成$m+1$个联通块,且每个联通块里至少有一个叶子。

要证明:对于所有将图断开成$m+1$个联通块且均内含叶子的图,一定存在一种连叶子的方式满足条件。

$m=1$时直接把俩点连起来即可。

最开始我们有$m+1$个联通块和$2m$个叶子。$2 \times (m+1) >2m$,一定存在一个联通块里面只有一个叶节点。

所以本侧至少有两个联通块,则对侧最多有$m-1$个联通块却有$m$个点,鸽巢原理可知,一定存在至少一个大小大于$1$的联通块。

这样我们把那个孤立点和联通块中任意一个没连过线的叶子连上。这样之后叶子$-2$联通块$-1$。(大小为$1$的消失,大于$1$的依然存在)

可以归纳证明了。

然后这样的话我们就只需要把原图劈成$m+1$个带叶子的联通块。跑个带限制的最大生成树即可(限制剩余带叶子联通块数不得少于$m+1$)

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 #define S 500005
 4 struct E{int a,b,w;friend bool operator<(E x,E y){return x.w>y.w;}}e[S];
 5 int n,sz[S],fir[S],l[S],to[S],ec,f[S],c; long long ans;
 6 void link(int a,int b){l[++ec]=fir[a];fir[a]=ec;to[ec]=b;}
 7 void con(int a,int b){link(a,b);link(b,a);}
 8 void dfs(int p,int fa){
 9     sz[p]=1;
10     for(int i=fir[p];i;i=l[i])if(to[i]!=fa)dfs(to[i],p),sz[p]+=sz[to[i]];
11 }
12 int find(int x){return f[x]==x?x:f[x]=find(f[x]);}
13 int main(){
14     scanf("%d",&n);
15     for(int i=1,a,b,x;i<n;++i)scanf("%d%d%d",&a,&b,&x),e[i]=(E){a,b,x},link(a,b),link(b,a),ans+=x;
16     for(int i=1,a,b,x;i<n;++i)scanf("%d%d%d",&a,&b,&x),a+=n,b+=n,e[i+n-1]=(E){a,b,x},link(a,b),link(b,a),ans+=x;
17     dfs(1,0);dfs(n+1,0);
18     for(int i=1;i<=n+n;++i)sz[i]=sz[i]==1,f[i]=i;
19     sz[1]=sz[n+1]=0; f[n+1]=1;
20     for(int i=1;i<=n;++i)c+=sz[i];
21     c--;
22     sort(e+1,e+1+n-1+n-1);
23     for(int i=1;i<=n-1+n-1;++i){
24         int x=find(e[i].a),y=find(e[i].b);
25         if(x==y)continue;
26         if(sz[x]&&sz[y]&&!c)continue;
27         c-=sz[x]&sz[y]; f[x]=y; sz[y]|=sz[x]; ans-=e[i].w;
28     }cout<<ans<<endl;
29 }
View Code

 

posted @ 2020-05-11 20:18  DeepinC  阅读(232)  评论(0编辑  收藏  举报