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]]);
}
}