Loading

2017-2018 Petrozavodsk Winter Training Camp, Saratov SU Contest 部分题解

2017-2018 Petrozavodsk Winter Training Camp, Saratov SU Contest 部分题解

A.Three Arrays

题意

给定三个长度分别为\(n_a,n_b,n_c\)的有序数组\(a,b,c\)

计算三元组个数\((i,j,k)\)使得\(|a_i - b_j| \leq d,|a_i - c_k| \leq d,|b_j - c_k|\leq d\)

分析

可以发现这个条件是比较强的,直接算不好算。我们钦定最小值,即枚举每个元素作为最小值的方案数

注意到会有重复计算,如同样枚举1作为最小值,可能有(1,1,1)被算多次。

去重方法就是下一次计算不要算大于等于,算大于就行了

代码

#include<bits/stdc++.h>
#define re register
using namespace std;

typedef long long ll;

inline int rd(){
	int x = 0;
	int f = 1;
	char ch = getchar();
	while(ch < '0' || ch > '9') {
		if(ch == '-') f = -1;
		ch = getchar();
	}
	while(ch >= '0' && ch <= '9') {
		x = x * 10 + ch - '0';
		ch = getchar();
	}
	return x * f;
}

int main(){
	int d;
	while(~scanf("%d",&d)){
		int na = rd();
		int nb = rd();
		int nc = rd();
		vector<int> v1(na),v2(nb),v3(nc);
		unordered_map<int,int> mp1,mp2,mp3;
		for(int i = 0;i < na;i++)
			v1[i] = rd(),mp1[v1[i]]++;
		for(int i = 0;i < nb;i++)
			v2[i] = rd(),mp2[v2[i]]++;
		for(int i = 0;i < nc;i++)
			v3[i] = rd(),mp3[v3[i]]++;
		ll ans = 0;
		for(int i = 0;i < na;i++){
			int cnt1 = upper_bound(v2.begin(),v2.end(),v1[i] + d) - lower_bound(v2.begin(),v2.end(),v1[i]);
		   	int cnt2 = upper_bound(v3.begin(),v3.end(),v1[i] + d) - lower_bound(v3.begin(),v3.end(),v1[i]);	
			ans += (ll)cnt1 * cnt2;
		}
		for(int i = 0;i < nb;i++){
			int cnt1 = upper_bound(v1.begin(),v1.end(),v2[i] + d) - upper_bound(v1.begin(),v1.end(),v2[i]);
		   	int cnt2 = upper_bound(v3.begin(),v3.end(),v2[i] + d) - lower_bound(v3.begin(),v3.end(),v2[i]);	
			ans += (ll)cnt1 * cnt2;
		}
		for(int i = 0;i < nc;i++){
			int cnt1 = upper_bound(v1.begin(),v1.end(),v3[i] + d) - upper_bound(v1.begin(),v1.end(),v3[i]);
			int cnt2 = upper_bound(v2.begin(),v2.end(),v3[i] + d) - upper_bound(v2.begin(),v2.end(),v3[i]);
			ans += (ll)cnt1 * cnt2;
		}
		printf("%lld\n",ans);
	}
}

C. Cover the Paths

代码

#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int mod=1e9+7;
const int maxn=1e5+10;

int n,m;
vector<int>g[maxn];
int vis[maxn];
vector<int>ans;
map<pair<int,int>,int>mp;
set<int>st[maxn];

void dfs(int u,int fa)
{
    for(auto v:g[u]){
        if(v==fa)continue;
        dfs(v,u);

        if(st[u].size()>st[v].size()){
            for(auto now:st[v]){
                auto pos=st[u].find(now);
                if(pos!=st[u].end()){
                    vis[u]=1;
                }else{
                    st[u].insert(now);
                }
            }
        }else{
            for(auto now:st[u]){
                auto pos=st[v].find(now);
                if(pos!=st[v].end()){
                    vis[u]=1;
                }else{
                    st[v].insert(now);
                }
            }
            swap(st[u],st[v]);
        }
    }

    if(vis[u]){
        ans.push_back(u);
        st[u].clear();
    }
}
int main()
{
    cin>>n;
    for(int i=1;i<n;i++){
        int u,v;
        scanf("%d%d",&u,&v);
        g[u].push_back(v);
        g[v].push_back(u);
    }
    cin>>m;
    for(int i=1;i<=m;i++){
        int x,y;
        scanf("%d%d",&x,&y);
        if(x>y)swap(x,y);
        if(x==y){
            //ans.push_back(x);
            vis[x]=1;
        }
        if(mp[{x,y}]==1)continue;
        st[x].insert(i);
        st[y].insert(i);
        mp[{x,y}]=1;
    }
    dfs(1,0);
    cout<<ans.size()<<endl;
    for(auto now:ans){
        printf("%d ",now);
    }
}

D. Elevator

题意

\(n\)个人从0层开始坐电梯到指定层数,电梯的容量是无限的。

每个人有到达时间和指定层,电梯移动一层花费1单位时间。问把所有人送到指定层并回到0层的总时间是多少。

分析

首先可以对问题简化。我们只需要得到非递增的指定层序列,如 2 5 3 4 2 3。

完全可以简化为5 4 3,容易证明这样的策略不会更劣。

然后可以考虑枚举上次一次上典题的位置,容易得出转移方程\(dp_i = min_j\{max\{dp_j + 2h_{i+1},t_i + 2h_{i+1}\}\}\)

取max是因为必须要等到那个时刻典题到达0层,且第i个人到达。

这题很好的性质在于dp和t都是单调的,于是可以用类似双指针的方法再配合堆来优化复杂度 ,具体细节不做解释

代码

#include<bits/stdc++.h>
#define fi first
#define se second
#define pii pair<ll,ll>
#define re register

using namespace std;

typedef long long ll;

ll rd(){
	ll x = 0;
	char ch = getchar();
	while(ch < '0' || ch > '9') {
		ch = getchar();
	}
	while(ch >= '0' && ch <= '9'){
		x = x * 10 + ch - '0';
		ch = getchar();
	}
	return x;

}

const int maxn = 2e5 + 5;

int main(){
	int n;
	while(~scanf("%d",&n)){
	vector<int> t,a;
	vector<ll> dp(n + 1);
	for(int i = 1;i <= n;i++){
		int tt = rd();
		int aa = rd();
		while(!a.empty() && aa >= a.back()) a.pop_back(),t.pop_back();
		a.push_back(aa);
		t.push_back(tt);
	}
	priority_queue<pii,vector<pii>,greater<pii>> q;
	int now = 0;
	for(int i = 0;i < a.size();i++){
		while(now < i && dp[now] <= t[i]) now++;
		dp[i] = t[i] + (ll)2 * a[now];
		while(!q.empty()) {
			pii p = q.top();
			if(p.fi <= t[i] + 2 * a[p.se + 1])
			   	q.pop();
			else {
				dp[i] = min(dp[i],p.fi);
				break;
			}	
		}
		if(i + 1 != a.size())q.push(make_pair(dp[i] + 2 * a[i + 1],i));
	}
	printf("%lld\n",dp[a.size() - 1]);
	}
}

F.GCD

题意

给定数组\(a_1...a_n\) ,可以至多删除\(k(k \leq n/2)\)个数 求最大的GCD({a})

分析

怎么利用\(k \leq n / 2\)这个条件,其实这是一个很常见的套路。

考虑最后留下的\(a\)数组中至少会有原数组的一半。那么任意指定一个元素,其不存在原数组的概率\(p \leq 1 / 2\)

如果随机指定10个数均不在原数组中的概率是\(p \leq (1/2)^{10}\)

所以可以随机下标,假定该数必然在原数组中,然后进行计算。

若该数在原数组中,答案就是它的某个因子。问题变为了求它的每个因子在其他元素中出现次数,答案就是最大的出现次数大于等于\(n - k\)的因子

因子个数大概是\(n^{1/3}\)级别,如果直接暴力算的话复杂度\(O(a_i^{1/3}n)\)显然会爆

所以用类似高维前缀和方法,把质因子看成维数,就可以做到\(O(KT)\) 处理出质因子个数 \(K\)为质因子种类个数,\(T\)为因子个数

代码

#include<bits/stdc++.h>
#define fi first
#define se second
using namespace std;

typedef long long ll;

ll rd(){
	ll x;
	scanf("%lld",&x);
	return x;
}

ll ans;
int n,k;

void calc(set<ll,greater<ll>> &s,unordered_map<ll,int> &mp,ll x){
	for(auto it:s){
		if(!(it % x)) {
			mp[it / x] += mp[it];
			s.insert(it / x);
		}
	}
}

inline ll solve(vector<ll> &a,ll x){
	set<ll,greater<ll>> s;
	unordered_map<ll,int> mp;
	for(int i = 0;i < n;i++)
		mp[__gcd(a[i],x)]++;
	for(auto it:mp){
		s.insert(it.fi);
	}
	for(ll i = 2;(ll) i * i * i <= x;i++){
		if(!(x % i)) {
			while(!(x % i)) x /= i;
			calc(s,mp,i);
			//s.insert(i);	
		}
	}
	if(x != 1) {
		ll tmp = sqrt(x);
		if(tmp * tmp < x) tmp++;
		if(tmp * tmp == x) calc(s,mp,tmp);
		else if((tmp + 1) * (tmp + 1) == x) calc(s,mp,tmp + 1);
		else {
			int flag = 0;
			for(auto it:s){
				ll g = __gcd(it,x);
				if(g != 1 && g != x){
					calc(s,mp,g);
					calc(s,mp,x / g);
					flag = 1;
					break;
				}
			}
			if(!flag) calc(s,mp,x);
		}
	}
	//for(auto it:s){
	//	cout << it << '\n';
	//}
	for(auto it:s){
		if(mp[it] >= n - k) return it;
	}
	return 1;
}

int main(){
	n = rd();
	k = rd();
	srand(0);
	vector<ll> a(n);
	for(int i = 0;i < n;i++)
		a[i] = rd();
	for(int i = 0;i < 20;i++){
		int pos = ((unsigned long long)rand() * rand()) % n;
		ans = max(ans,solve(a,a[pos]));
	}
	printf("%lld",ans);
}

J. Subsequence Sum Queries

题意

\(q\)个询问,每次询问\([l,r]\)的满足\(sum \equiv 0 (mod \ m)\)子序列的个数

分析

如果没有区间需求,显然是很简单的线性DP 加上询问可以考虑分治,应该算比较典型的分治+DP 见之前写过的博客

P7482 不条理狂诗曲 分治 DP - MQFLLY - 博客园 (cnblogs.com)

注意一下边界即可

代码

#include<bits/stdc++.h>
#define fi first
#define se second
#define pii pair<pair<int,int>,int>
#define re register

using namespace std;

typedef long long ll;

ll rd(){
	ll x;
	scanf("%lld",&x);
	return x;
}

const int maxn = 2e5 + 5;
const int MOD = 1e9 + 7;

int a[maxn];
int dp1[maxn][25];
int dp2[maxn][25];
int ans[maxn];
int n,m;

void add(int &a,int b){
	a += b;
	if(a >= MOD) a-= MOD;
}

void solve(int l,int r,vector<pii>& Q){
	if(l >= r || Q.empty()) {
		return;
	}
	int mid = l + r >> 1;
	for(int i = l;i <= r;i++)
		for(int j = 0;j <= m;j++)
			dp1[i][j] = dp2[i][j] = 0;
	dp1[mid + 1][0] = 1;
	for(int i = mid;i >= l;i--)
		for(int j = 0;j < m;j++)
			add(dp1[i][j],dp1[i + 1][j]),add(dp1[i][(a[i] + j) % m],dp1[i + 1][j]);
	dp2[mid][0] = 1;
	for(int i = mid + 1;i <= r;i++)
		for(int j = 0;j < m;j++)
			add(dp2[i][j],dp2[i - 1][j]),add(dp2[i][(a[i] + j) % m],dp2[i - 1][j]);
	/*
	for(int i = l;i <= r;i++){
		for(int j = 0;j < m;j++){
			cout << "i = " << i  << ' ' << "j = " << j << ' ' << dp1[i][j] << ' ' << dp2[i][j] << '\n'; 
		}
	}*/
	vector<pii> L,R;
	for(auto it:Q){
		if(it.fi.fi > mid && it.fi.fi != it.fi.se) R.push_back(it);
		else if(it.fi.se < mid && it.fi.fi != it.fi.se) L.push_back(it);
		else if(it.fi.se != it.fi.fi){
			for(int j = 0;j < m;j++)
				add(ans[it.se],(ll)dp1[it.fi.fi][j] * dp2[it.fi.se][!j ?  0 : m - j] % MOD);
		}
	}
	solve(l,mid - 1,L);
	solve(mid + 1,r,R);
}

int main(){
	n = rd();
	m = rd();
	for(int i = 1;i <= n;i++)
		a[i] = rd(),a[i] %= m;
	int q = rd();
	vector<pii> Q(q + 1);
	for(int i = 1;i <= q;i++){
		Q[i].fi.fi = rd();
		Q[i].fi.se = rd();
		Q[i].se = i;
		if(Q[i].fi.fi == Q[i].fi.se) {
			a[Q[i].fi.fi] % m == 0 ? ans[Q[i].se] +=2 : ans[Q[i].se]++;
		}
	}
	solve(1,n,Q);
	for(int i = 1;i <= q;i++)
		printf("%d\n",ans[i]);	
}

K. Consistent Occurrences

题意

给定一个主串\(s\),\(n\)个模式串

模式串的总长度不超过\(1e5\)

求每个模式串能够在主串匹配多少次(不能重合)

分析

直接做显然不太好做(AC自动机和SAM可做?)

注意到最多\(1e5\)个串,总长不超过\(1e5\),可以得到结论串长种类是\(O(sqrt)\)级别的

于是只需要对询问离线后Hash即可,再用\(map\)记录上次匹配位置和哈希值

代码

#include<bits/stdc++.h>
#define fi first
#define se second
#define re register

using namespace std;

typedef long long ull;
//typedef long long ll;

ull rd(){
	ull x;
	scanf("%lld",&x);
	return x;
}

const int maxn = 1e5 + 5;
const ull P1 = 131;
const ull P2 = 13331;
const ull MOD1 = 1e9 + 7;
const ull MOD2 = 1e9 + 9;
const ull MOD3 = 998244353;

ull p1[maxn],p2[maxn];
ull f1[maxn],f2[maxn];
ull H[maxn];
char s[maxn];
char ss[maxn];

inline void add1(ull &a,ull b){
	a += b;
	if(a >= MOD1) a -= MOD1;
}

inline void add2(ull &a,ull b){
	a += b;
	if(a >= MOD2) a -= MOD2;
}

inline ull getHash1(int l,int len){
	return f1[l + len - 1] - f1[l - 1] * p1[len] % MOD1 + MOD1;
}

inline ull getHash2(int l,int len){
	return f2[l + len - 1] - f2[l - 1] * p2[len] % MOD2 + MOD2;
}

int main(){
	int n = rd();
	int m = rd();
	scanf("%s",s + 1);
	map<pair<ull,ull>,int> mp;
	map<pair<ull,ull>,int> last;
	map<int,pair<ull,ull>> H;
	unordered_map<ull,int> st;
	p1[0] = p2[0] = 1;
	for(re int i = 1;i < maxn;i++)	
		p1[i] = p1[i - 1] * P1 % MOD1,p2[i] = p2[i - 1] * P2 % MOD2;
	for(re int i = 1;i <= n;i++)
		f1[i] = f1[i - 1] * P1 % MOD1,add1(f1[i],s[i] - 'a' + 1),f2[i] = f2[i - 1] * P2 % MOD2,add2(f2[i],s[i] - 'a' + 1);
	for(re int i = 1;i <= m;i++){
		scanf("%s",ss + 1);
		int len = strlen(ss + 1);
		ull tmp1 = 0;
		ull tmp2 = 0;
		for(re int j = 1;j <= len;j++){
			tmp1 = tmp1 * P1 % MOD1,add1(tmp1,ss[j] - 'a' + 1);
			tmp2 = tmp2 * P2 % MOD2,add2(tmp2,ss[j] - 'a' + 1);
		}
		pair<ull,ull> g = make_pair(tmp1,tmp2);
		mp[g] = 0,last[g] = 0;
		H[i] = g;
		st[len] = 1;
	}
	for(auto it :st){
		for(int i = 1;i + it.fi - 1 <= n;i++){
			ull g1 = getHash1(i,it.fi);
			ull g2 = getHash2(i,it.fi);
			if(g1 >= MOD1) g1 -= MOD1;
			if(g2 >= MOD2) g2 -= MOD2;
			pair<ull,ull> g = make_pair(g1,g2);
			if(mp.count(g) && last[g] < i) {
				last[g] = i + it.fi - 1;
				mp[g]++;
			}	
		}
	}
	for(int i = 1;i <= m;i++){
		printf("%d\n",mp[H[i]]);
	}	
}
posted @ 2021-07-08 14:23  MQFLLY  阅读(441)  评论(0编辑  收藏  举报