Processing math: 100%

牛客多校第四场

补题进度: 5/8(10)
自闭了啊,感觉脑子不行了


A

题意

给一个字符串s,由0,1,2组成,每一秒都会在1后面加一个0, 2后面加一个1,并且第一个字符消失,问多少秒钟s变为空。答案%109+7 , (|s|2·106)

题解

分析可得出

  • 考虑0,直接删掉即可 ,1次
  • 考虑1,假设前面操作了x次,那么删除它以及它生成的1所需要 2·x+2
  • 考虑2,暴力打表,观察可得 3·2x+1

直接快速幂从左向右递推的话相当于很多个log相乘肯定不行

考虑欧拉函数降幂

一个经典的问题:
abc..%p

可用欧拉函数降幂解决

ab=ab % ϕ(p)%p     (gcd(a,p)=1)

但需要不断模数是变化的, 即不断的迭代 phi(phi(mod))

考虑欧拉降幂公式,mod是从小到大的,即后面模数大于前面的模数,预处理phi(phi(mod)),反向迭代mod即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn = 1e5+100;
const int mod = 1e9+7;
char s[maxn];
int n,T;

int p[maxn];

ll quick_pow(ll a, ll b, ll mod){
ll ans=1;
while(b){
if(b&1) ans=(ans*a)%mod;
a=(a*a)%mod;
b>>=1;
}
return ans;
}

int phi(int n){
int ans=n;
for(int i=2;i*i<=n;i++){
if(n && n%i==0){
ans=ans/i*(i-1);
while(n%i==0)
n/=i;

}
}
if(n>1) ans=ans/n*(n-1);
return ans;
}

void init(){ // 预处理phi
p[0]=mod;
int k=1;
while( k < 30){
p[k] = phi(p[k-1]);
k++;
}
for(int i=k;i<maxn;i++)
p[i]=1;
}

int main(){
init();
scanf("%d", &T);
while(T--){
scanf("%s", s+1);
ll ans=0;
int len=strlen(s+1);
int cnt=0;
for(int i=1;i<=len;i++)
if(s[i]=='2') cnt++;
for(int i=1;i<=len;i++){
if(s[i]=='1') ans= (2*ans+2)%p[cnt];
else if(s[i]=='0') ans=(ans+1)%p[cnt];
else{
--cnt;
ans = ( (3 * quick_pow(2, (ans+1)%p[cnt], p[cnt]))%p[cnt] - 3 + p[cnt] ) % p[cnt];
}
}
printf("%lld\n", ans);
}
return 0;
}


B

题意

给一个大区间[1,m], n个小区间[li,ri],每个小区间都有一个权重wi, 现要从这n个区间选择几个小区间使得覆盖[1,m],每个位置的权重si等于这个位置上小区间的权重wi的和,问所有si最大的最小的值

题解

待补

dp+线段树


C

题意

an=0(n=1)
an=an/2+(1)n(n+1)/2(n>=2)

ni=1|ai|
答案%109+7(T105,n1018)

题解

待补
找规律


D

题意

给一个n,输出一个只由1,0,1构成的每一行的和以及每一列的和都不相同,行和列也不相同,n·n的矩阵,不存在1

题解

找几个数玩玩发现

  • n为奇数不存在
  • n为偶数瞎构造构造
    1
    2
    3
    4
    1  1  1  1
    0 -1 1 1
    0 0 1 1
    0 0 0 -1

E

题意

平⾯上n个点,每个点出现概率是pi,⼀个点被⽀配,当且仅当存在⼀个点在其右上⽅, 求期望被⽀配的点数。(n1e5)

题解

待补
概率dp


F

模拟即可


G

题意

给一个长度为n序列的a, 问你删除m后,出现次数最多(不能出现次数最多的数大于一个)且最大的数是几,不存在-1

题解

暴力枚举每个数,check每个数要最少删除的数量

check方法: priority_queue,从大最小弹即可

1
2
3
pq 默认从大到小弹出
pq<int> = pq<int, vector<int>, greater<int> >; 从小到大
pq<int, vector<int>, less<int> >; 从大到小


H

题意

给一个字符串s,问有多少对(i,j),交换(si,sj)后,s是两个回文串

题解


I

留坑


J

题意

给一个长度为 nhash表,位置为[0n1]h(x)=x%n, 若这个位置已经被其他的数占据,则位置+1, 直至找到合法的位置,现给出hash表,问你原来的字典序最小的序列,hash表不合法输出1

题解

这种有明显的先后关系,可以转化为有向图后拓扑排序,一个很直白的建边方式是,一个点和它[h(x)%ni],之间的所有点建边,这样最坏情况下是n2
,可以用线段树优化建图后跑拓扑排序即可。

解法二:并查集

考虑每个点安放顺序,一个点应安放[h(x)%ni]之后,考虑并查集,每个点安放好后把这个点的father设为右边一个点的father,这样原本不能建的点会变的合法,不断check下一个位置即可,那么这个点合法安放的条件是father[i]=father[a[i]%n], 字典序最小的话用优先队列优先弹出小的值即可。

Trick:注意每次的下一个位置不一定是(pos+1)%n, 而是findfa((pos+1)%n)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
#include<bits/stdc++.h>
#define ll long long
#define sz(x) (int)(x).size()
using namespace std;
const int maxn = 2e5+100;
const int mod = 1e9+7;
int n,T;

int fa[maxn], a[maxn], ans[maxn];
bool vis[maxn];

int findfa(int x){
if(fa[x]==x) return x;
else return fa[x]=findfa(fa[x]);
}

void init(){
memset(vis, false, sizeof(vis));
for(int i=0;i<maxn;i++)
fa[i]=i;

}

struct node{
int a, pos;
friend bool operator < (node x, node y){
return x.a > y.a;
}
};

int main()
{
scanf("%d",&T);
while(T--){
init();
priority_queue<node>q;
scanf("%d",&n);
int cnt=0;
for(int i=0;i<n;i++)
{
scanf("%d", &a[i]);
if(a[i]%n==i && a[i]!=-1) q.push({a[i], i}), vis[i]=1;
if(a[i]!=-1) ++cnt;
}
int num=0;
while(sz(q))
{
node now = q.top();
q.pop();
ans[++num]=now.a;
fa[findfa(now.pos)] = findfa((now.pos+1)%n);
int k=fa[now.pos];
if(findfa(a[k]%n) == k && (!vis[k]) && a[k]!=-1)
{
q.push({a[k], k});
vis[k]=1;
}
}
if(cnt==0)
cout<<endl;
else if(num == cnt){
for(int i=1;i<=num;i++){
printf("%d%c", ans[i], i==num?'\n':' ');
}
}
else
cout<<"-1"<<endl;
}
}