A: abb
给出一个字符串,问最少在后面添加多少个字符使得该字符串是个回文串。
对字符串构造后缀自动机,然后反方向跑,跑时判断是否是结尾。
int last,idx;
int nt[N][30],len[N],pre[N];
void init(){
_clr(pre);
clr(len);
clr(nt);
idx=1;
last = 0;
}
void add(int c){
int cur = idx++;
len[cur] = len[last]+1;
int p = last;
for(; p!=-1 && !nt[p][c]; p = pre[p])nt[p][c] = cur;
if(p==-1){
pre[cur] = 0;
}
else{
int q = nt[p][c];
if(len[p]==len[q]+1){
pre[cur] = q;
}
else {
int new_cur = idx++;
memcpy(nt[new_cur], nt[q],sizeof(nt[q]));
pre[new_cur] = pre[q];
len[new_cur] = len[p]+1;
for(;p!=-1 && nt[p][c]==q; p = pre[p]) nt[p][c] = new_cur;
pre[q] = new_cur;
pre[cur] = new_cur;
}
}
last = cur;
}
int main(){
init();
int n;
cin>>n;
string s;
cin>>s;
for(int i = 0; i < n; ++i){
add(s[i]-'a');
}
add(27);
int cur = 0;
int ans = 0;
for(int i = n-1; i >=0;--i){
if(!nt[cur][s[i]-'a'])break;
cur = nt[cur][s[i]-'a'];
if(nt[cur][27]){
ans = n-i;
}
}
printf("%d\n",n-ans);
}
B: Be Geeks!
问
首先枚举max (i,j), 然后就是怎么算在这个区间内的gcd了。
假设 最大值为a[x]的区间是[i,j], 那么gcd(a[i],a[i+1]), gcd(a[i],a[i+1],a[i+2]) ... gcd(a[i],a[i+1],a[i+1]...a[x])是单调的。而且最多只有log(a[x])个值。
为什么? 因为每一个值都是后一个比他大的值的因子。极端情况下,a[x] = 2^30, 那么前面的值就是2^29...2^28 .... 2。
因此可以二分出这些区间。同理,右边也一样。
得到左右的区间值 [l_pos1, l_pos2... l_pos_i] [r_pos1, r_pos2, r_pos_i]后,则可以枚举合并区间然后计算该区间的gcd和总和了
int a[N], lt[N],rt[N];
int gd[N][30];
int gcd(int a, int b) {
return b?gcd(b,a%b):a;
}
int get_gcd(int l, int r){
int len = r-l+1;
int k = 0;for(; 1<<k <=len; k++);
k--;
return gcd(gd[l][k], gd[r-(1<<k)+1][k]);
}
int main(){
int n;
cin>>n;
fr(i,0,n){
cin>>a[i];
}
stack<int> st;
fr(i,0,n){
rt[i] = n-1;
while(!st.empty()&&a[st.top()] <a[i]) rt[st.top()]=i-1, st.pop();
if(st.empty()) lt[i] = 0;
else lt[i] = st.top()+1;
gd[i][0] = a[i];
st.push(i);
}
for(int i = 1; (1<<i) <=n; ++i){
for(int j = 0; j + (1<<i) <=n; ++j){
gd[j][i] = gcd(gd[j][i-1], gd[j+(1<<(i-1))][i-1]);
}
}
ll ans = 0;
fr(i,0,n){
int l = lt[i], r = rt[i];
int cur = i;
vector<pair<int,int>> l_pos, r_pos;
while(l<=cur){
int cur_l = l-1, cur_r = cur;
int cur_gcd = get_gcd(cur,i);
while(cur_l+1<cur_r){
int mid = (cur_l+cur_r)>>1;
if(get_gcd(mid,i) == cur_gcd) cur_r = mid;
else cur_l = mid;
}
l_pos.pb(mp(cur_r,cur-cur_r+1));
cur = cur_r-1;
}
cur = i;
while(cur<=r){
int cur_l = cur, cur_r = r+1;
int cur_gcd = get_gcd(i,cur);
while(cur_l+1<cur_r){
int mid = (cur_l+cur_r)>>1;
if(get_gcd(i,mid) == cur_gcd) cur_l = mid;
else cur_r = mid;
}
r_pos.pb(mp(cur_l,cur_l-cur+1));
cur = cur_l+1;
}
for(auto l_p : l_pos) for(auto r_p : r_pos){
int cur_gcd = get_gcd(l_p.first, r_p.first);
int l_len = l_p.second, r_len = r_p.second;
ans = (ans + ((1ll*l_len*r_len)%mod * cur_gcd) %mod * a[i])%mod;
}
}
cout<<ans<<endl;
}
C:Bob in Wonderland
给出一棵树,要把它变成一条链,问需要操作多少次(把一个节点挂在另外一个节点下)。
每次操作都会把一棵子树挂在其中一个子节点下,相当于每次操作都减少一个子节点。最后得到的链的子节点(度数为1)的个数为2。
因此操作次数为度数为1的点的个数-2.
只有一个点时需要特判。
int d[N];
int main(){
int n;
cin>>n;
fr(i,0,n-1){
int u,v;
cin>>u>>v;
d[u]++;
d[v]++;
}
int num = 0;
fr(i,1,n+1) if(d[i]==1) num++;
printf("%d\n",max(num-2,0));
}
D: Deep800080
貌似需要推公式,先腻
E: Zeldain Garden
问[n,m]中的数的因子个数的和。
等价于问[1,n]中的因子个数的和。也等价于问[1,n]中有多少对(a,b),a*b<=n。
a的大小为[1,sqrt(n)]。然后计算b>sqrt(n)的个数,因为小于等于sqrt(n)的会在a被统计。
ll num(ll n){
ll sum = 0;
ll x = sqrt(n);
for(ll i = 1; i*i<=n;++i){
ll t = n/i;
sum += (n/i) + t-min(t,x);
}
return sum;
}
int main(){
ll n,m;
cin>>n>>m;
printf("%lld\n",num(m)-num(n-1));
}
F: Light Emitting Hindenburg
题目根本不知道在说什么,大意就是给出n个数,选出k个数,他们的&操作结果最大。
从最高位开始判断当前位是否有大于k的数,如果是,那么那些数可以继续用,其他数不能再用了。
因此只需要对每一位记录下当前那些数可以统计的就可以了。
#include<bits/stdc++.h>
using namespace std;
int v[1000010];
int p[31][200010];
int main(){
int n,k;
cin>>n>>k;
vector<int> gl;
for(int i = 0; i < n; ++i){
int x;
cin>>x;
v[i] = 1;
for(int j = 0; (1<<j)<=x;++j){
if((1<<j)&x){
p[j][i]=1;
}
}
}
int ans = 0;
for(int i = 30; i>=0;--i){
int num = 0;
for(int j = 0; j <n;++j){
if(p[i][j]==0||v[j]==0)continue;
num++;
}
if(num>=k){
ans |= 1<<i;
for(int j = 0; j < n;++j){
if(p[i][j]==0)v[j]=0;
}
}
}
cout<<ans<<endl;
}
G: K==S
给出q个串,问构造出一个长度为n的串,不包含这q个串的方案数。
对这q个串构造一个ac自动机,
如果构造出了一个串,那么去跑这个ac自动机的时候不能经过end节点。
相当于问根节点到所有非end子节点的方案数。
对ac自动机转化成邻接矩阵,对于u,v的边,如果都不是end节点则为1,否则为0.
然后跑矩阵快速幂,最后统计根节点到所有节点的路径和。
int mod = 1e9+7;
int get_min(int a, int b){
return a==-1? b: (b==-1?a:min(a,b));
}
ll get_min(ll a, ll b){
return a==-1? b: (b==-1?a:min(a,b));
}
int sz;
int t[201][30],ed[201], fail[201];
void add(string &s){
int root = 0;
for(int i = 0; i < s.size();++i){
int c = s[i]-'a';
if(!t[root][c])t[root][c]=sz++;
root = t[root][c];
}
ed[root] = 1;
}
void make_fail(){
queue<int> q;
for(int i = 0; i < 26; ++i) if(t[0][i])q.push(t[0][i]);
while(!q.empty()){
int v = q.front();q.pop();
for(int i = 0; i < 26; ++i){
if(!t[v][i]) t[v][i] = t[fail[v]][i];
else {
int u = t[v][i];
fail[u] = t[fail[v]][i];
ed[u] |= ed[fail[u]];
q.push(u);
}
}
}
}
int A[201][201];
int ans[201][201];
void mul(int A[201][201],int B[201][201]){
int C[201][201];
clr(C);
for(int i = 0; i < sz; ++i)for(int j = 0; j < sz; ++j) for(int k = 0; k < sz; ++k){
C[i][j] = (1ll*A[i][k] * B[k][j] + C[i][j])%mod;
}
for(int i = 0; i < sz; ++i) for(int j = 0; j < sz; ++j) A[i][j] = C[i][j];
}
void pw(int A[201][201],int b){
clr(ans);
for(int i = 0; i < sz; ++i) ans[i][i] = 1;
while(b>0){
if(b&1) mul(ans, A);
mul(A,A);
b>>=1;
}
}
int main(){
int n,q;
cin>>n>>q;
sz = 1;
while(q--){
int m;
cin>>m;
string s;
cin>>s;
add(s);
}
make_fail();
for(int i = 0; i < sz; ++i){
for(int j = 0; j < 26; ++j){
if(ed[i] || ed[t[i][j]])continue;
A[i][t[i][j]]+=1;
}
}
pw(A,n);
ll sum = 0;
for(int i = 0; i < sz; ++i)sum = (sum + ans[0][i]) % mod;
cout<<sum<<endl;
}
H: Ponk Warshall
给出两个只有ACGT 的字符串,对第二个字符串做字符交换,问至少需要多少次操作得到第一个字符串。
相当于一个置换环,环的长度只有2,3和4 3种。从小到大枚举长度,操作次数为长度-1.
#include <stdio.h>
#include <string>
#include <vector>
#include <map>
#include <iostream>
#define fr(i,a,b) for(int i = a; i <b; ++i)
using namespace std;
int g[256][256];
int main(){
string s1,s2;
cin>>s1>>s2;
int n = s1.size();
vector<char>p{'A','C','G','T'};
map<char,int> id; for(int i =0; i < 4; ++i)id[p[i]]=i;
for(int i = 0;i < n; ++i){
if(s1[i]!=s2[i]){
g[id[s1[i]]][id[s2[i]]]++;
}
}
int ans = 0;
fr(i,0,4)fr(j,0,4){
if(g[i][j]>0&&g[j][i]>0){
int t = min(g[i][j],g[j][i]);
g[i][j]-=t;g[j][i]-=t;
ans+=t;
}
}
fr(i,0,4)fr(j,0,4)fr(k,0,4){
if(g[i][j]&&g[j][k]&&g[k][i]){
int t = min(g[i][j],min(g[j][k],g[k][i]));
ans += t*2;
g[i][j]-=t;g[j][k]-=t;g[k][i]-=t;
}
}
fr(i,0,4)fr(j,0,4)fr(k,0,4)fr(l,0,4){
if(g[i][j]&&g[j][k]&&g[k][l]&&g[l][i]){
int t = min(min(g[i][j],g[j][k]),min(g[k][l],g[l][i]));
ans += t*3;
g[i][j]-=t;g[j][k]-=t;g[k][l]-=t;g[l][i]-=t;
}
}
cout<<ans<<endl;
}
I: Saba1000kg
给出n个点m条边的图。q次询问,每次询问给出k个点,问这k个点组成的子图一共有多少个连通块。
有两种做法:
1. 直接枚举k个点重新构建一张图,复杂度是o(k*k*log(m)) log(m) 是需要判断两个点是否有边
2. 枚举k个点然后枚举他的边,这样复杂度就是o(m)
但是q的范围是1e5,因此会爆。
可是总的查询节点数是1e5 (这是一个很重要的信号!) 当k小于sqrt(1e5)的时候,用第一种,大于等于时用第二种。
因此每一种的查询数最多sqrt(1e5),总复杂度就是sqrt(1e5)*(sqrt(1e5)*sqrt(1e5)*log(m) + m)
这题的重要hins是总结点数有限制,然后刚好有两种处理方法!而且分界线必须是sqrt(1e5)而不是sqrt(n)
using namespace std;
//int mod = 998244353;
int mod = 1e9+7;
int get_min(int a, int b){
return a==-1? b: (b==-1?a:min(a,b));
}
ll get_min(ll a, ll b){
return a==-1? b: (b==-1?a:min(a,b));
}
double eps = 1e-5;
double dis(double x0, double y0, double x1, double y1){
double x = x0-x1, y = y0-y1;
return sqrt(x*x+y*y);
}
set<int> g[N];
int v[N];
int fa[N];
int tmp[N];
int f(int t){
if(fa[t]!=t) fa[t] = f(fa[t]);
return fa[t];
}
int main(){
int n,m,q;
cin>>n>>m>>q;
fr(i,0,m){
int u,v;
cin>>u>>v;
g[u].insert(v);
g[v].insert(u);
}
fr(i,1,n+1) fa[i] = i;
while(q--){
int k;
cin>>k;
fr(i,0,k){
sf("%d",&tmp[i]);
fa[tmp[i]]=tmp[i];
}
int x = sqrt(1e5); //必须是1e5
if(k<x){
fr(i,0,k){
fr(j,i+1,k){
if(g[tmp[i]].find(tmp[j])!=g[tmp[i]].end()){
//printf("add %d %d\n",tmp[i],tmp[j]);
fa[f(tmp[i])] = f(tmp[j]);
}
}
}
}
else {
fr(i,0,k) v[tmp[i]]=1;
fr(i,0,k){
for(int u: g[tmp[i]]){
if(!v[u])continue;
fa[f(tmp[i])] = f(u);
}
}
fr(i,0,k) v[tmp[i]]=0;
}
int ans = 0;
fr(i,0,k)if(f(tmp[i])==tmp[i])ans++;
pf("%d\n",ans);
}
}
J: The Bugs
不会
本文介绍了牛客国庆集训的算法题目,涉及字符串回文、区间最大GCD计算、树形结构变化、因子个数求和、位运算优化、AC自动机应用、字符交换问题及图的连通块查询等算法问题,是算法初学者的良好练习资料。
390

被折叠的 条评论
为什么被折叠?



