C. Assembly via Remainders
思路:
我们可以注意到,数组的长度只有 500 500 500 ,并且每个数的大小都在 500 500 500 以内,再看向这题,容易知道,当第一个数确定之后,之后所有的数字都会确定下来,我们枚举第一个数,然后得到整个序列,去判断序列是否合法即可。
#include <bits/stdc++.h>
using namespace std;
const int N = 1e6 + 5;
typedef long long ll;
typedef pair<int, int> pll;
typedef array<ll, 3> p3;
// int mod = 998244353;
const int maxv = 4e6 + 5;
// #define endl "\n"
void solve()
{
int n;
cin>>n;
vector<int> a(n);
for(int i=1;i<n;i++) cin>>a[i];
vector<int> ans(n);
for(int i=1;i<=500+5;i++){
ans[0]=i;
for(int j=1;j<n;j++) ans[j]=ans[j-1]+a[j];
int f=1;
for(int j=1;j<n;j++){
if(ans[j]%ans[j-1]!=a[j]){
f=0;
break;
}
}
if(f){
for(auto x: ans) cout<<x<<" ";
cout<<endl;
return ;
}
}
}
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
int t;
t = 1;
cin >> t;
while (t--)
{
solve();
}
system("pause");
return 0;
}
D. Permutation Game
思路:
容易知道,最优解一定是走到某个格子然后呆在该格子上即可,所以我们可以直接枚举走到的格子,然后取最大值。
#include <bits/stdc++.h>
using namespace std;
const int N = 2e5 + 5;
typedef long long ll;
typedef pair<int, int> pll;
typedef array<ll, 3> p3;
// int mod = 998244353;
const int maxv = 4e6 + 5;
#define endl "\n"
vector<int> e[N];
vector<ll> a(N);
int n,k;
void add(int u,int v){
e[u].push_back(v);
}
bool st1[N],st2[N];
ll ans1=0,ans2=0;
void dfs1(int x,int time,ll sum)
{
ll res=0;
st1[x]=1;
res=sum+a[x]*time;
ans1=max(ans1,res);
for(auto u: e[x]){
if(st1[u]) continue;
if(time==1) return ;
dfs1(u,time-1,sum+a[x]);
}
}
void dfs2(int x,int time,ll sum)
{
ll res=0;
st2[x]=1;
res=sum+a[x]*time;
ans2=max(ans2,res);
for(auto u: e[x]){
if(st2[u]) continue;
if(time==1) return ;
dfs2(u,time-1,sum+a[x]);
}
}
void solve()
{
int s1,s2;
cin>>n>>k>>s1>>s2;
// k=min(k,n);
memset(st1,0,sizeof st1);
memset(st2,0,sizeof st2);
ans1=ans2=0;
for(int i=1;i<=n;i++) e[i].clear();
vector<int> p(n+5);
for(int i=1;i<=n;i++){
cin>>p[i];
add(i,p[i]);
}
for(int i=1;i<=n;i++) cin>>a[i];
// cin>>s1>>s2;
dfs1(s1,k,0);
dfs2(s2,k,0);
// cout<<ans1<<" "<<ans2<<endl;
if(ans1>ans2) cout<<"Bodya"<<endl;
else if(ans1<ans2 ) cout<<"Sasha"<<endl;
else cout<<"Draw"<<endl;
}
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
int t;
t = 1;
cin >> t;
while (t--)
{
solve();
}
system("pause");
return 0;
}
E. Cells Arrangement
思路:
**构造没什么好说的。
#include <bits/stdc++.h>
using namespace std;
const int N = 1e6 + 5;
typedef long long ll;
typedef pair<int, int> pll;
typedef array<ll, 3> p3;
// int mod = 998244353;
const int maxv = 4e6 + 5;
// #define endl "\n"
void solve()
{
int n;
cin>>n;
if(n==2){
cout<<1<<" "<<1<<endl;
cout<<1<<" "<<2<<endl;
}
else{
for(int i=1;i<=n;i++){
if(i==n-1){
cout<<n-1<<" "<<n<<endl;
}
else cout<<i<<" "<<i<<endl;
}
}
}
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
int t;
t = 1;
cin >> t;
while (t--)
{
solve();
}
system("pause");
return 0;
}
F. Equal XOR Segments
思路
题目要求我们把给定的区间划分成
k
k
k 个,保证每一部分的异或值都相同,
q
q
q 次询问,涉及到多次询问,还有区间异或问题,我们很容易想到,需要把异或前缀和数组给处理出来。
进而,我们考虑最简单的一种情况,即,整个区间的异或和为
0
0
0 时,这时候肯定是一个好数组,那么若区间异或和不为
0
0
0 ,这种时候,我们肯定只能把数组划分成奇数段,同时我们也会发现,当
k
=
3
,
5
,
7
,
9
k=3,5,7,9
k=3,5,7,9 等等都是等效的,拿
5
5
5 来举例,我们可以将两个合并成一个
0
0
0 ,然后这样就只剩下三个部分。所以综上所述,这题只存在划分成两段或者是三段的情况。
再来考虑三段的情况如何进行处理:
如图,很容易得到,
s
[
x
]
=
s
[
r
]
s[x]=s[r]
s[x]=s[r],
s
[
l
−
1
]
=
s
[
y
]
s[l-1]=s[y]
s[l−1]=s[y],所以我们可以把前缀异或值相同的下标放入一个桶中,然后我们去二分查找对应的
x
,
y
x,y
x,y 是否合法即可,即
l
≤
x
<
y
≤
r
−
1
l\leq x<y\leq r-1
l≤x<y≤r−1。
#include <bits/stdc++.h>
using namespace std;
const int N = 1e6 + 5;
typedef long long ll;
typedef pair<int, int> pll;
typedef array<ll, 3> p3;
// int mod = 998244353;
const int maxv = 4e6 + 5;
#define endl "\n"
void solve()
{
int n,q;
cin>>n>>q;
vector<ll> a(n+5),s(n+5);
map<ll,vector<int> > mp;
mp[0].push_back(0);
for(int i=1;i<=n;i++){
cin>>a[i];
s[i]=s[i-1]^a[i];
mp[s[i]].push_back(i);
}
while(q--){
int l,r;
cin>>l>>r;
if((s[l-1]^s[r])==0){
cout<<"YES"<<endl;
continue;
}
auto &v1=mp[s[l-1]];
auto &v2=mp[s[r]];
int y=lower_bound(v1.begin(),v1.end(),r)-v1.begin()-1;
int x=lower_bound(v2.begin(),v2.end(),l)-v2.begin();
if(y==v1.size()||y<0||x<0||x==v2.size()){
cout<<"NO"<<endl;
continue;
}
if(v1[y]<r&&v2[x]>=l&&v1[y]>v2[x]){
cout<<"YES"<<endl;
}
else cout<<"NO"<<endl;
}
}
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
int t;
t = 1;
cin >> t;
while (t--)
{
solve();
}
system("pause");
return 0;
}
G2. Division + LCP (hard version)
思路:
首先考虑简单版本,即 l = r l=r l=r 的情况,首先我们对整个字符串求出其 z z z 函数,其表示为从当前位置 i i i 开始与整个字符串 s s s 的最长公共前缀,那么接下来我们可以去二分 l c p lcp lcp 的长度,得到二分后的段数 c n t cnt cnt ,然后将 c n t cnt cnt 和 l l l ,进行比较即可,很明显这个东西具有单调性,当我们二分的长度越小时,得到的 c n t cnt cnt 一定是越大的。
#include <bits/stdc++.h>
using namespace std;
const int N = 1e6 + 5;
typedef long long ll;
typedef pair<int, int> pll;
typedef array<ll, 3> p3;
// int mod = 998244353;
const int maxv = 4e6 + 5;
// #define endl "\n"
vector<int> z_algorithm(const string &s)
{
int n = s.size();
vector<int> a(n);
a[0] = n;
for (int i = 1, l = 0, r = 0; i < n; i++)
{
if (i <= r)
a[i] = min(a[i - l], r - i + 1);
while (i + a[i] < n && s[i + a[i]] == s[a[i]])
++a[i];
if (i + a[i] - 1 > r)
l = i, r = i + a[i] - 1;
}
return a;
}
void solve()
{
int n, kl, kr;
cin >> n >> kl >> kr;
string s;
cin >> s;
vector<int> z = z_algorithm(s);
int l = 1, r = n;
auto check = [&](int x)
{
int cnt = 0;
for (int i = 0; i < n; i++)
{
if (z[i] >= x)
{
cnt++;
i = i + x - 1;
}
}
return cnt >= kl;
};
int ans = 0;
while (l <= r)
{
int mid = (l + r) / 2;
if (check(mid))
{
ans = mid;
l = mid + 1;
}
else
{
r = mid - 1;
}
}
cout << ans << endl;
}
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
int t;
t = 1;
cin >> t;
while (t--)
{
solve();
}
system("pause");
return 0;
}
再考虑复杂版本,我们需要求一段区间的
f
f
f值,我们是否还是可以利用简单版本的做法,当然是可以的,容易发现,最终的一个答案也具有单调性,即随着段数
k
k
k的上升,我们对应得到的答案一定是越来越小的,即
l
c
p
lcp
lcp 的长度一定会越来越小,其长度为
n
k
n\over k
kn 。由此我们完全可以进行根号分治,当
k
k
k 小于某个阈值时,我们暴力的去运用简单版本的二分得到答案,当
k
k
k 大于该阈值时,我们就暴力的去枚举
l
c
p
lcp
lcp 的长度即可,因为最多只会枚举
n
k
n\over k
kn次。
最终的时间复杂度为
n
n
l
o
g
n
+
n
n
\sqrt nnlogn+n \sqrt n
nnlogn+nn 。
#include <bits/stdc++.h>
using namespace std;
const int N = 1e6 + 5;
typedef long long ll;
typedef pair<int, int> pll;
typedef array<ll, 3> p3;
// int mod = 998244353;
const int maxv = 4e6 + 5;
// #define endl "\n"
vector<int> z_algorithm(const string &s)
{
int n = s.size();
vector<int> a(n);
a[0] = n;
for (int i = 1, l = 0, r = 0; i < n; i++)
{
if (i <= r)
a[i] = min(a[i - l], r - i + 1);
while (i + a[i] < n && s[i + a[i]] == s[a[i]])
++a[i];
if (i + a[i] - 1 > r)
l = i, r = i + a[i] - 1;
}
return a;
}
int cal(int tar,int n,vector<int> &z)//二分暴力计算
{
int l = 1, r = n;
auto check = [&](int x)
{
int cnt = 0;
for (int i = 0; i < n; i++)
{
if (z[i] >= x)
{
cnt++;
i = i + x - 1;
}
}
return cnt >= tar;
};
int ans = 0;
while (l <= r)
{
int mid = (l + r) / 2;
if (check(mid))
{
ans = mid;
l = mid + 1;
}
else
{
r = mid - 1;
}
}
return ans;
}
void solve()
{
int n, kl, kr;
cin >> n >> kl >> kr;
string s;
cin >> s;
vector<int> z = z_algorithm(s);//计算z函数
int bk=(int)sqrt(n);//阈值
vector<int> f(n+5);
for(int len=1;len<=bk;len++){//大于阈值,暴力枚举长度
int cnt=0;
for(int i=0;i<n;i++){
if(z[i]>=len){
i=i+len-1;
cnt++;
}
}
f[cnt]=len;
}
for(int i=n;i>=0;i--) f[i]=max(f[i],f[i+1]);//后缀取最大值即可,因为具有单调性
for(int i=kl;i<=kr;i++){
if(i<=bk){
cout<<cal(i,n,z)<<" ";
}
else cout<<f[i]<<" ";
}
cout<<endl;
}
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
int t;
t = 1;
cin >> t;
while (t--)
{
solve();
}
system("pause");
return 0;
}