前言
这是第一次补完 div2 吧!!!!!
一、A. Flip Flops
cpp
#include <bits/stdc++.h>
using namespace std;
/* /\_/\
* (= ._.)
* / > \>
*/
/*
*想好再写
*注意审题 注意特判
*不要红温 不要急躁 耐心一点
*WA了不要立马觉得是思路不对 先耐心找反例
*/
#define endl '\n'
#define dbg(x) cout<<#x<<endl;cout<<x<<endl;
#define vdbg(a) cout<<#a<<endl;for(auto x:a)cout<<x<<" ";cout<<endl;
#define YES cout<<"YES"<<endl;return ;
#define Yes cout<<"Yes"<<endl;return ;
#define NO cout<<"NO"<<endl;return ;
#define No cout<<"No"<<endl;return ;
typedef long long ll;
typedef long double ld;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
const int INF=1e9;
const ll INFLL=1e18;
const int dx[]={-1,1,0,0};
const int dy[]={0,0,-1,1};
const int ddx[]={-2,-1,1,2,2,1,-1,-2};
const int ddy[]={1,2,2,1,-1,-2,-2,-1};
void solve()
{
ll n,c,k;
cin>>n>>c>>k;
vector<ll>a(n+1);
for(int i=1;i<=n;i++)
{
cin>>a[i];
}
priority_queue<ll,vector<ll>,greater<ll>>heap;
for(int i=1;i<=n;i++)
{
heap.push(a[i]);
}
ll ans=c;
while(!heap.empty())
{
ll cur=heap.top();
heap.pop();
if(cur>ans)
{
break;
}
if(cur<=ans)
{
ll add=ans-cur;
cur+=min(add,k);
k-=min(add,k);
}
ans+=cur;
}
cout<<ans<<endl;
}
void init()
{
}
signed main()
{
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
int t=1;
cin>>t;
init();
while(t--)
{
solve();
}
return 0;
}
因为每次只能增加,所以最多就是把小于 c 的怪兽激怒到 c 再杀。所以拿个小根堆维护一下,每次模拟即可。
二、B. Array
cpp
#include <bits/stdc++.h>
using namespace std;
/* /\_/\
* (= ._.)
* / > \>
*/
/*
*想好再写
*注意审题 注意特判
*不要红温 不要急躁 耐心一点
*WA了不要立马觉得是思路不对 先耐心找反例
*/
#define endl '\n'
#define dbg(x) cout<<#x<<endl;cout<<x<<endl;
#define vdbg(a) cout<<#a<<endl;for(auto x:a)cout<<x<<" ";cout<<endl;
#define YES cout<<"YES"<<endl;return ;
#define Yes cout<<"Yes"<<endl;return ;
#define NO cout<<"NO"<<endl;return ;
#define No cout<<"No"<<endl;return ;
typedef long long ll;
typedef long double ld;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
const int INF=1e9;
const ll INFLL=1e18;
const int dx[]={-1,1,0,0};
const int dy[]={0,0,-1,1};
const int ddx[]={-2,-1,1,2,2,1,-1,-2};
const int ddy[]={1,2,2,1,-1,-2,-2,-1};
void solve()
{
int n;
cin>>n;
vector<ll>a(n+1);
for(int i=1;i<=n;i++)
{
cin>>a[i];
}
for(int i=1;i<=n;i++)
{
int less=0;
int cnt=n-i;
for(int j=i+1;j<=n;j++)
{
if(a[j]<a[i])
{
less++;
}
else if(a[j]==a[i])
{
cnt--;
}
}
cout<<max(less,cnt-less)<<" ";
}
cout<<endl;
}
void init()
{
}
signed main()
{
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
int t=1;
cin>>t;
init();
while(t--)
{
solve();
}
return 0;
}
因为 n 不大,所以可以每次暴力。之后对于绝对值,可以考虑将其放到数轴上观察,可以发现当前数左侧和右侧只能满足一边,所以统计一下严格小于和严格大于的个数,比较即可。
三、C. Find the Zero
cpp
#include <bits/stdc++.h>
using namespace std;
/* /\_/\
* (= ._.)
* / > \>
*/
/*
*想好再写
*注意审题 注意特判
*不要红温 不要急躁 耐心一点
*WA了不要立马觉得是思路不对 先耐心找反例
*/
#define dbg(x) cout<<#x<<endl;cout<<x<<endl;
#define vdbg(a) cout<<#a<<endl;for(auto x:a)cout<<x<<" ";cout<<endl;
#define YES cout<<"YES"<<endl;return ;
#define Yes cout<<"Yes"<<endl;return ;
#define NO cout<<"NO"<<endl;return ;
#define No cout<<"No"<<endl;return ;
typedef long long ll;
typedef long double ld;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
const int INF=1e9;
const ll INFLL=1e18;
const int dx[]={-1,1,0,0};
const int dy[]={0,0,-1,1};
const int ddx[]={-2,-1,1,2,2,1,-1,-2};
const int ddy[]={1,2,2,1,-1,-2,-2,-1};
int ask(int i,int j)
{
cout<<"? "<<i<<" "<<j<<endl;
int res;
cin>>res;
return res;
}
void solve()
{
int n;
cin>>n;
for(int i=1,j=1;i<=n-1;i++,j+=2)
{
int res=ask(j,j+1);
if(res)
{
cout<<"! "<<j<<endl;
return ;
}
}
int res=ask(2*n,2*n-2);
if(res)
{
cout<<"! "<<2*n<<endl;
return ;
}
res=ask(2*n,2*n-3);
if(res)
{
cout<<"! "<<2*n<<endl;
return ;
}
cout<<"! "<<2*n-1<<endl;
}
void init()
{
}
signed main()
{
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
int t=1;
cin>>t;
init();
while(t--)
{
solve();
}
return 0;
}
由于每次只能判断是否相等,且剩下的数构成一个排列,那么只有当两元素都是 0 时才会导致相等。
又因为整个序列有一半是 0,那么如果将其两两分为一组,此时若存在一组里没有 0,那么就必然有一组里都是 0。所以可以先考虑顺着两两一组问,若相等就直接返回即可。
若问到最后都没有,就说明 0 是均匀分到每一组里的。此时可以考察最后的两组,从一组里选一个元素和另一组的某个元素问。但此时可以发现,这样需要两次查询,而又因为之前用了 n 次,所以需要考虑优化。
考虑最后一组不询问,那么就节省出一次查询。之后用最后一个元素去问倒数第二组里的两元素,若相等就直接返回。而若都不相等,又因为倒数第二组里的两元素不同,那么打表可以发现,此时最后一组另一个元素必然是 0。
四、D. Ghostfires
这构史题......
cpp
#include <bits/stdc++.h>
using namespace std;
/* /\_/\
* (= ._.)
* / > \>
*/
/*
*想好再写
*注意审题 注意特判
*不要红温 不要急躁 耐心一点
*WA了不要立马觉得是思路不对 先耐心找反例
*/
#define endl '\n'
#define dbg(x) cout<<#x<<endl;cout<<x<<endl;
#define vdbg(a) cout<<#a<<endl;for(auto x:a)cout<<x<<" ";cout<<endl;
#define YES cout<<"YES"<<endl;return ;
#define Yes cout<<"Yes"<<endl;return ;
#define NO cout<<"NO"<<endl;return ;
#define No cout<<"No"<<endl;return ;
typedef long long ll;
typedef long double ld;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
const int INF=1e9;
const ll INFLL=1e18;
const int dx[]={-1,1,0,0};
const int dy[]={0,0,-1,1};
const int ddx[]={-2,-1,1,2,2,1,-1,-2};
const int ddy[]={1,2,2,1,-1,-2,-2,-1};
void solve()
{
int r,g,b;
cin>>r>>g>>b;
vector<pair<int,char>>a={{r,'R'},{g,'G'},{b,'B'}};
sort(a.begin(),a.end());
int add=((a[0].first+a[1].first)%2)^(a[2].first%2);
string ans;
while(a[0].first&&a[0].first+a[1].first+add>a[2].first)
{
ans+=a[0].second;
a[0].first--;
ans+=a[1].second;
a[1].first--;
}
while(a[1].first)
{
ans+=a[2].second;
a[2].first--;
ans+=a[1].second;
a[1].first--;
}
while(a[0].first)
{
ans+=a[2].second;
a[2].first--;
ans+=a[0].second;
a[0].first--;
}
if(a[2].first)
{
ans+=a[2].second;
a[2].first--;
}
cout<<ans<<endl;
}
void init()
{
}
signed main()
{
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
int t=1;
cin>>t;
init();
while(t--)
{
solve();
}
return 0;
}
设三种字符按个数从大到小排序后是 A,B,C,之后通过打表和观察样例可以发现,要么是 ABAB 这样,要么是 ABAC 这样,要么是 ABCBCA循环。而对于前两种,因为两个限制都只限制奇数长度后,即奇偶性和当前位置不同的位置,所以两个两个拼是一定不会违法的。
首先,考虑什么时候词频是用不完的。那么可以发现,最多就是一个 A 和一个 B 或 C 配对,之后在结尾再拼一个 A。所以设三者的个数分别为 x,y,z,那么若 x 大于 y+z+1,就说明必然用不完。而当 x 等于 y+z 或 y+z+1 时,就可以通过 ABAB......ACAC 的方式完成构造。对于 +1 的情况,就只需要判断一下 x 和 y+z 的奇偶性,若不同就需要 +1 修改成相同的奇偶性。那么当 x 小于 y+z 时,就可以通过 BCBC 这样将 y+z 的个数压到和 x 一样。
太脑电波了......
五、E. A Trivial String Problem
cpp
#include <bits/stdc++.h>
using namespace std;
/* /\_/\
* (= ._.)
* / > \>
*/
/*
*想好再写
*注意审题 注意特判
*不要红温 不要急躁 耐心一点
*WA了不要立马觉得是思路不对 先耐心找反例
*/
#define endl '\n'
#define dbg(x) cout<<#x<<endl;cout<<x<<endl;
#define vdbg(a) cout<<#a<<endl;for(auto x:a)cout<<x<<" ";cout<<endl;
#define YES cout<<"YES"<<endl;return ;
#define Yes cout<<"Yes"<<endl;return ;
#define NO cout<<"NO"<<endl;return ;
#define No cout<<"No"<<endl;return ;
typedef long long ll;
typedef long double ld;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
const int INF=1e9;
const ll INFLL=1e18;
const int dx[]={-1,1,0,0};
const int dy[]={0,0,-1,1};
const int ddx[]={-2,-1,1,2,2,1,-1,-2};
const int ddy[]={1,2,2,1,-1,-2,-2,-1};
void solve()
{
int n,q;
cin>>n>>q;
string str;
cin>>str;
str=" "+str;
int l,r;
while(q--)
{
cin>>l>>r;
int m=r-l+1;
string s=str.substr(l,m);
vector<int>next(m+1);
next[0]=-1;
if(m>1)
{
next[1]=0;
int i=2,cn=0;
while(i<=m){
if(s[i-1]==s[cn]){
next[i++]=++cn;
}
else if(cn>0){
cn=next[cn];
}
else{
next[i++]=0;
}
}
}
vector<int>dp(m);
ll ans=0;
for(int i=0;i<m;i++)
{
if(next[i+1]>0)
{
dp[i]=dp[i-next[i+1]]+dp[next[i+1]-1];
}
else
{
dp[i]=1;
}
ans+=dp[i];
}
cout<<ans<<endl;
}
}
void init()
{
}
signed main()
{
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
int t=1;
cin>>t;
init();
while(t--)
{
solve();
}
return 0;
}
注意到查询的次数很少,所以可以每次查询 O(n) 计算。
Trick:对于字符串问题,首先考虑能否 dp。
因为要求线性复杂度,所以考虑定义 为在 i 位置结尾,能划分出的最大子串个数。那么根据定义,最终答案就是
。
当来到每个位置 i 时,考虑其一直到前面哪个位置被划分为最后一个子串。那么对于当前要划分出去的最后一个子串,其首先必然是当前字符串的一个后缀,而且还需要是这个字符串的一个前缀串。
回顾 KMP 算法中 next 数组的定义,即不含当前字符且不包含整体字符串,前面字符串前后缀的最大匹配长度。可以发现此时最后一个子串的最长长度,其实就是 的答案,那么其就可以从
位置转移过来。之后,对于最后一部分子串,其必然有可能继续划分,可以发现这个就是
的答案了,两部分相加即可。而若
小于等于 0,那么就说明不存在这样的划分,那么此时的答案就是 1,即整个字符串。
六、F. Dynamic Values And Maximum Sum
cpp
#include <bits/stdc++.h>
using namespace std;
/* /\_/\
* (= ._.)
* / > \>
*/
/*
*想好再写
*注意审题 注意特判
*不要红温 不要急躁 耐心一点
*WA了不要立马觉得是思路不对 先耐心找反例
*/
#define endl '\n'
#define dbg(x) cout<<#x<<endl;cout<<x<<endl;
#define vdbg(a) cout<<#a<<endl;for(auto x:a)cout<<x<<" ";cout<<endl;
#define YES cout<<"YES"<<endl;return ;
#define Yes cout<<"Yes"<<endl;return ;
#define NO cout<<"NO"<<endl;return ;
#define No cout<<"No"<<endl;return ;
typedef long long ll;
typedef long double ld;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
const int INF=1e9;
const ll INFLL=1e18;
const int dx[]={-1,1,0,0};
const int dy[]={0,0,-1,1};
const int ddx[]={-2,-1,1,2,2,1,-1,-2};
const int ddy[]={1,2,2,1,-1,-2,-2,-1};
template<typename T>
struct TwoHeap{
public:
TwoHeap(int _k){
k=_k;
up=0;
down=0;
}
void push(T x){
if(!heap1.empty()&&x<=*heap1.rbegin()){
heap1.insert(x);
up+=x;
}else{
heap2.insert(x);
down+=x;
}
tidy();
}
void erase(T x){
if(!heap1.empty()&&x<=*heap1.rbegin()){
auto iter=heap1.find(x);
if(iter!=heap1.end()){
up-=*iter;
heap1.erase(iter);
}
}else{
auto iter=heap2.find(x);
if(iter!=heap2.end()){
down-=*iter;
heap2.erase(iter);
}
}
tidy();
}
//k-th value
T top(){
if(heap2.empty()){
return 0;
}
return *heap2.begin();
}
void setK(int _k){
k=_k;
tidy();
}
int size(){
return heap1.size()+heap2.size();
}
T query(){
return down;
}
private:
multiset<T>heap1;//big root heap
multiset<T>heap2;//small root heap
int k;
T up,down;
void tidy(){
while(!heap1.empty()&&heap2.size()<k){
auto iter=prev(heap1.end());
down+=*iter;
heap2.insert(*iter);
up-=*iter;
heap1.erase(iter);
}
while(heap2.size()>k){
auto iter=heap2.begin();
up+=*iter;
heap1.insert(*iter);
down-=*iter;
heap2.erase(iter);
}
}
};
void solve()
{
int n,k;
cin>>n>>k;
vector<ll>a(n+1);
for(int i=1;i<=n;i++)
{
cin>>a[i];
}
vector<vector<int>>g(n+1);
for(int i=1,u,v;i<=n-1;i++)
{
cin>>u>>v;
g[u].push_back(v);
g[v].push_back(u);
}
if(k==1)
{
ll ans=0;
for(int i=1;i<=n;i++)
{
ans=max(ans,a[i]);
}
cout<<ans<<endl;
return ;
}
vector<ll>best(n+1);
vector<ll>bestFrom(n+1);
vector<ll>choose(n+1);
vector<ll>sec(n+1);
vector<ll>secFrom(n+1);
auto updateInner=[&](int u,int v)->void
{
if(best[v]+1>best[u]||(best[v]+1==best[u]&&bestFrom[v]<bestFrom[u]))
{
sec[u]=best[u];
secFrom[u]=bestFrom[u];
best[u]=best[v]+1;
bestFrom[u]=bestFrom[v];
choose[u]=v;
}
else if(best[v]+1>sec[u]||(best[v]+1==sec[u]&&bestFrom[v]<secFrom[u]))
{
sec[u]=best[v]+1;
secFrom[u]=bestFrom[v];
}
};
auto dfs1=[&](auto &&self,int u,int fa)->void
{
best[u]=0;
bestFrom[u]=u;
for(auto v:g[u])
{
if(v!=fa)
{
self(self,v,u);
updateInner(u,v);
}
}
};
dfs1(dfs1,1,0);
vector<ll>val(n+1);
for(int i=2;i<=n;i++)
{
val[bestFrom[i]]+=a[i];
if(bestFrom[i]!=i)
{
val[i]=0;
}
}
TwoHeap<ll>heap(k-1);
for(int i=1;i<=n;i++)
{
if(val[i])
{
heap.push(val[i]);
}
}
vector<ll>outer(n+1);
vector<ll>outerFrom(n+1);
auto updateOuter=[&](int u,int v)->void
{
if(choose[u]!=v)
{
if(best[u]+1>outer[u]+1||(best[u]+1==outer[u]+1&&bestFrom[u]<outerFrom[u]))
{
outer[v]=best[u]+1;
outerFrom[v]=bestFrom[u];
}
else
{
outer[v]=outer[u]+1;
outerFrom[v]=(u==1?u:outerFrom[u]);
}
}
else
{
if(sec[u]+1>outer[u]+1||(sec[u]+1==outer[u]+1&&secFrom[u]<outerFrom[u]))
{
outer[v]=sec[u]+1;
outerFrom[v]=secFrom[u];
}
else
{
outer[v]=outer[u]+1;
outerFrom[v]=(u==1?u:outerFrom[u]);
}
}
};
ll ans=0;
auto dfs2=[&](auto &&self,int u,int fa)->void
{
ans=max(ans,a[u]+heap.query());
for(auto v:g[u])
{
if(v!=fa)
{
updateOuter(u,v);
heap.erase(val[outerFrom[v]]);
heap.erase(val[bestFrom[v]]);
val[outerFrom[v]]+=a[u];
val[bestFrom[v]]-=a[v];
heap.push(val[outerFrom[v]]);
heap.push(val[bestFrom[v]]);
self(self,v,u);
heap.erase(val[outerFrom[v]]);
heap.erase(val[bestFrom[v]]);
val[outerFrom[v]]-=a[u];
val[bestFrom[v]]+=a[v];
heap.push(val[outerFrom[v]]);
heap.push(val[bestFrom[v]]);
}
}
};
dfs2(dfs2,1,0);
cout<<ans<<endl;
}
void init()
{
}
signed main()
{
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
int t=1;
cin>>t;
init();
while(t--)
{
solve();
}
return 0;
}
好题哇!!
不难发现,在第一次操作完之后,除了叶子节点以外,其他所有点的点权都是 0 了。那么之后就只需要从所有叶子节点中选出最大的 k-1 个,将其加到总和里即可。
首先,对于动态维护前 K 大数字及其累加和,就可以通过对顶堆实现。简单说一下原理,就是用一个小根堆维护前 K 大的数字,用一个大根堆维护剩下的数字。每次添加后,关注两个堆里是否需要往另一个堆里转移数字,如果需要的话直接转移堆顶的数字即可。
之后就需要考虑第一次选哪个节点作为根能让总和最大了,那么这个就可以考虑换根 dp 了。那么在第一次 dfs 构建以 1 节点为根时的答案时,就需要求出此时每个节点的点权操作一次后会去往哪个叶子节点。那么这个就是每个节点的最大深度 best,还有对应的最深的叶子节点编号 bestFrom。之后就是构建一个 val 数组,表示以当前节点为根操作一次后,每个节点的点权,然后根据 bestFrom,对除了 1 节点以外的节点操作,然后加入对顶堆维护前 K-1 大的累加和即可。
在换根的时候,首先当前节点 v 需要从叶子节点里把点权拿回来。又因为当 u 为根时,v 去往的节点必然是自己这棵子树内最深的叶子节点,所以可以通过 bestFrom 直接查找。之后,还需要让父亲节点 u 把点权分配给最深的叶子节点。此时,由于是以 v 为根,所以 u 的子树就变成所有 v 的外部节点,包括 u 的外部节点和 v 的兄弟节点。那么因为需要让 u 的外部节点和 v 的兄弟节点决出一个最深叶子,所以在第一次 dfs 时还需要维护出 u 的最深叶子节点从哪个孩子中选出的 choose,以及次深深度 sec 和次深深度的叶子节点 secFrom。
所以之后就是每次求出 v 外部最深深度 outer 和对应的叶子节点 outerFrom,即讨论 v 是否是 u 最深的孩子,注意当 u 是 1 时外部最深叶子是 u 本身。 那么在即将去往 v 节点,讨论以 v 为根时的答案时,由于当前对顶堆维护的是以 u 为根时的前 K-1 大,所以需要进行修改。方法就是让 v 的点权回来,再让 u 的点权去往 outerFrom 即可,注意每次递归完回来要还原一下。
总结
继续努力,争取做到每次 div2 都能补完!!