2020牛客国庆集训派对day1

本文介绍了牛客国庆集训的算法题目,涉及字符串回文、区间最大GCD计算、树形结构变化、因子个数求和、位运算优化、AC自动机应用、字符交换问题及图的连通块查询等算法问题,是算法初学者的良好练习资料。

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!

\sum \sum gcd(i,j)*Max(i,j)

首先枚举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和总和了 gcd([left pos,right pos]) * a[x] * left len * right len

 


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

不会

 

 

 

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值