文章目录
I. Perfect Palindrome
签到
G. Inscryption
签到,反悔贪心
D. Chat Program
题意:
给定一个序列a,和整数k,m,c,d,可以进行最多一次操作,将长度为m的连续部分的每个数,与首项为c,公差为d的等比数列相加,最大化序列的第k大值
思路:
- 维护长度为m的滑动窗口,每次移动一个单位,滑动窗口的首项会从c+id变成c+id+d,相当于整体序列+d,维护一个偏移量,对每个滑动窗口二分答案即可,计算>=mid的数有多少个,可以使用权值树状数组来查找
时间复杂度: O ( n l o g 2 n ) O(nlog^2n) O(nlog2n)
#include<bits/stdc++.h>
#include<ext/pb_ds/assoc_container.hpp>
#include<ext/pb_ds/tree_policy.hpp>
#define int long long
using namespace std;
using namespace __gnu_pbds;
typedef pair<int,int> PII;
const int N=1e5+10;
struct BIT{
vector<int> c;
int n;
void init(int nn){
n=nn;
c=vector<int> (nn);
}
void add(int x,int y){
for(int i=x;i<=n;i+=i&-i)c[i]+=y;
}
int query(int x){
int ans=0;
for(int i=x;i;i&=i-1)ans+=c[i];
return ans;
}
};
void solve(){
int n,k,m,c,d;
cin>>n>>k>>m>>c>>d;
vector<int> a(n+1);
for(int i=1;i<=n;i++)cin>>a[i];
vector<int> nums;
for(int i=1;i<=n;i++)nums.push_back(a[i]),nums.push_back(a[i]+c+(i-1)*d);
sort(nums.begin(),nums.end());
nums.erase(unique(nums.begin(),nums.end()),nums.end());
auto id=[&](int x){
int t=lower_bound(nums.begin(),nums.end(),x)-nums.begin()+1;
return nums.size()-t+1;
};
BIT t1,t2;
t1.init(nums.size()+10);
t2.init(nums.size()+10);
for(int i=1;i<=n;i++)t2.add(id(a[i]),1);
int offset=0,ans=0;
auto check=[&](int mid){
int x=id(mid);
int res=t1.query(x);
mid-=offset;
{
x=id(mid);
res+=t2.query(x);
}
return res>=k;
};
for(int i=1,j=1;i<=n;i++){
t1.add(id(a[i]+c+(i-1)*d),1);
t2.add(id(a[i]),-1);
if(i-j+1>m){
t1.add(id(a[j]+c+(j-1)*d),-1);
t2.add(id(a[j]),1);
j++;
}
if(i-j+1==m){
int l=0,r=1e18;
while(l<r){
int mid=l+r+1>>1;
if(check(mid))l=mid;
else r=mid-1;
}
ans=max(ans,l-offset);
// cout<<ans<<" "<<i<<" "<<l<<" "<<offset<<"\n";
offset+=d;
}
}
cout<<ans<<"\n";
}
signed main(){
cin.tie(0)->sync_with_stdio(0);
int T=1;
// cin>>T;
while(T--)solve();
return 0;
}
A. Stop, Yesterday Please No More
题意:
给定一个n*m的网格,每个网格有个袋鼠,还有一个包含"U",“D”,“L”,"R"的操作序列,网格内还存在一个洞,执行完操作序列后,网格上还有k只袋鼠,询问洞的位置有多少个?
思路:
-
首先假设没有洞,那么最终剩余在网格上的袋鼠是一个矩形,这个矩形在移动时必然不会离开边界
-
因此求出这个矩形的左上顶点后,模拟这个矩形移动,每次对矩形内的所有点+1,最后每个网格的值即当洞在此时能掉进去的袋鼠数量
-
可以使用二维差分做,最后求和判断即可
时间复杂度: O ( ∣ S ∣ + n m ) O(|S|+nm) O(∣S∣+nm)
#include<bits/stdc++.h>
#define int long long
using namespace std;
typedef pair<int,int> PII;
const int N=1e3+10;
void solve(){
int n,m,k;
cin>>n>>m>>k;
vector<vector<int>> b(n+10,vector<int>(m+10));
string s;
cin>>s;
int L,R,U,D,l,r,u,d;
L=U=l=u=1,R=r=m,D=d=n;
for(auto ch:s){
if(ch=='L')l++,r++;
if(ch=='R')l--,r--;
if(ch=='D')u--,d--;
if(ch=='U')u++,d++;
L=max(L,l);
R=min(R,r);
D=min(D,d);
U=max(U,u);
}
if(L>R||U>D){
if(k==0)cout<<n*m<<"\n";
else cout<<"0\n";
return;
}
int area=max(R-L+1,0ll)*max(D-U+1,0ll);
area-=k;
if(area<0){
cout<<"0\n";
return;
}
int x=U,y=L;
int lenx=D-U+1,leny=R-L+1;
auto add=[&](){
int xx=x+lenx-1,yy=y+leny-1;
b[xx+1][yy+1]++;
b[x][y]++;
b[xx+1][y]--;
b[x][yy+1]--;
};
vector<vector<bool>> vis(n+1,vector<bool> (m+1));
vis[x][y]=1;
add();
for(auto ch:s){
if(ch=='L')y--;
else if(ch=='R')y++;
else if(ch=='U')x--;
else x++;
if(vis[x][y])continue;
vis[x][y]=1;
add();
}
int ans=0;
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
b[i][j]+=b[i][j-1]+b[i-1][j]-b[i-1][j-1];
if(b[i][j]==area)ans++;
}
}
cout<<ans<<"\n";
}
signed main(){
cin.tie(0)->sync_with_stdio(0);
int T=1;
cin>>T;
while(T--)solve();
return 0;
}
/*
3
4 5 3
ULDDRR
4 5 0
UUUUUUU
4 5 10
UUUUUUU
*/
M. Drain the Water Tank
题意:
给定n个顶点形成的多边形水箱,问需要在多边形开多少个口才能把水全部流出
1
≤
n
≤
2
⋅
1
0
3
1 \leq n \leq 2 \cdot 10^3
1≤n≤2⋅103
思路:
- 顶点逆时针给出,顶点x从左到右上方在多边形内部,从右到左上方在多边形外部
- 可以发现需要开点的形状要么是个凹下去的尖角,要么是凹下去的平面,判断即可
时间复杂度:
O
(
n
2
)
O(n^2)
O(n2)
#include<bits/stdc++.h>
#define int long long
using namespace std;
typedef pair<int,int> PII;
const int N=2e5+10;
int cross(int x1,int y1,int x2,int y2){
return x1*y2-x2*y1;
}
void solve(){
int n;
cin>>n;
vector<int> X(n),Y(n);
for(int i=0;i<n;i++)cin>>X[i]>>Y[i];
int ans=0;
for(int i=0,j=1;i<n;i++){
while(Y[i]==Y[j])j=(j+1)%n;
int pre=(i+n-1)%n;
if(Y[i]<Y[pre]&&Y[i]<Y[j]){
if(Y[i]!=Y[(i+1)%n]){
if(cross(X[i]-X[pre],Y[i]-Y[pre],X[j]-X[i],Y[j]-Y[i])>0)ans++;
}
else{
if(X[(i+1)%n]>X[i])ans++;
}
}
}
cout<<ans<<"\n";
}
signed main(){
cin.tie(0)->sync_with_stdio(0);
int T=1;
// freopen("in.txt","r",stdin);
// freopen("out.txt","w",stdout);
// cin>>T;
while(T--)solve();
return 0;
}
B. Ropeway
题意:
在距离索道入口0和(n+1)单位距离的位置有索道站,给
出在1,2,··· ,n 单位距离架设支撑塔的成本,分别是
a1, a2,··· ,an。要求相邻支撑塔或索道站之间的距离必须小
于等于k。
成本序列会进行q次临时的修改(之后会复原),求出架设
支撑塔的最小总成本。
1 ≤ n ≤ 5 ⋅ 1 0 5 , 1 ≤ k ≤ m i n ( n + 1 , 3 ⋅ 1 0 3 ) , 1 ≤ q ≤ 3 ⋅ 1 0 3 1\leq n \leq 5 \cdot 10^5 ,1 \leq k \leq min(n+1,3 \cdot 10^3),1 \leq q \leq 3 \cdot 10^3 1≤n≤5⋅105,1≤k≤min(n+1,3⋅103),1≤q≤3⋅103
思路:
- 假设没有修改,dp方程显然 f i 表示从 0 到 i 的最小代价, f i = m i n f j + a i ( i − j ≤ k ) f_i表示从0到i的最小代价,f_i=min f_j+a_i(i-j \leq k) fi表示从0到i的最小代价,fi=minfj+ai(i−j≤k)
- 可以使用单调队列优化成线性
- 此时加入修改,假设修改i,i后面的f都要变化
- 定义 g i 表示 i + 1 到 n + 1 的最小代价 , 则答案为 f i + g i 定义g_i表示i+1到n+1的最小代价,则答案为f_i+g_i 定义gi表示i+1到n+1的最小代价,则答案为fi+gi
- 此时改变 f i ,只需要重新计算从 i 开始后 k 位的 f 来重新贡献答案 此时改变f_i,只需要重新计算从i开始后k位的f来重新贡献答案 此时改变fi,只需要重新计算从i开始后k位的f来重新贡献答案
- q,k都很小,可以暴力更新一遍,最后复原即可
#include<bits/stdc++.h>
#define int long long
using namespace std;
typedef pair<int,int> PII;
void solve(){
int n,k;
cin>>n>>k;
vector<int> a(n+10),f(n+10),g(n+10),q(n+10);
for(int i=1;i<=n;i++)cin>>a[i];
string s;
cin>>s;
s=" "+s;
int hh=0,tt=-1;
for(int i=0;i<=n+1;i++){
while(hh<=tt&&i-q[hh]>k)hh++;
f[i]=f[q[hh]]+a[i];
if(s[i]=='1')hh=0,tt=-1;
while(hh<=tt&&f[q[tt]]>f[i])tt--;
q[++tt]=i;
}
hh=0,tt=-1;
for(int i=n+1;i>=0;i--){
while(hh<=tt&&q[hh]-i>k)hh++;
g[i]=g[q[hh]]+a[i];
if(s[i]=='1')hh=0,tt=-1;
while(hh<=tt&&g[q[tt]]>g[i])tt--;
q[++tt]=i;
}
for(int i=0;i<=n+1;i++)g[i]-=a[i];
int Q;
cin>>Q;
vector<int> F=f;
while(Q--){
int p,v;
cin>>p>>v;
int tmp=a[p];
a[p]=v;
hh=0,tt=-1;
for(int i=max(0ll,p-k);i<p;i++){
while(hh<=tt&&i-q[hh]>k)hh++;
if(s[i]=='1')hh=0,tt=-1;
while(hh<=tt&&f[q[tt]]>f[i])tt--;
q[++tt]=i;
}
for(int i=p;i<=min(p+k-1,n+1);i++)F[i]=f[i];
int res=1e18;
for(int i=p;i<=min(p+k-1,n+1);i++){
while(hh<=tt&&i-q[hh]>k)hh++;
f[i]=f[q[hh]]+a[i];
res=min(res,f[i]+g[i]);
if(s[i]=='1')hh=0,tt=-1;
while(hh<=tt&&f[q[tt]]>f[i])tt--;
q[++tt]=i;
}
cout<<res<<"\n";
for(int i=p;i<=min(p+k-1,n+1);i++)f[i]=F[i];
a[p]=tmp;
}
}
signed main(){
cin.tie(0)->sync_with_stdio(0);
int T=1;
// freopen("in.txt","r",stdin);
// freopen("out.txt","w",stdout);
cin>>T;
while(T--)solve();
return 0;
}
J. Perfect Matching
题意:
给定一张包含n个顶点的无向图(n是偶数)以及n个整数
a1, a2,··· ,an。对于任意满足 1 ≤ i <j ≤n 的正整数 i 和 j,
若|i−j| = |ai −aj|(|x| 表示 x 的绝对值)则在无向图中顶
点i 和顶点j之间连一条无向边。
求一个完美匹配,或表明不存在完美匹配。
完美匹配即用n/2条边将n个顶点匹配,任意点都有一条边相连
思路:
- i + a i = j + a j 或者 i − a i = j − a j 就有一条边 i+a_i=j+a_j或者i-a_i=j-a_j就有一条边 i+ai=j+aj或者i−ai=j−aj就有一条边
- 对于每个 i , i + a i 与 i − a i 连边,如果 i , j 匹配那么他们对应的边有一个公共点 对于每个i,i+a_i与i-a_i连边,如果i,j匹配那么他们对应的边有一个公共点 对于每个i,i+ai与i−ai连边,如果i,j匹配那么他们对应的边有一个公共点
- 问题转化成将一张图分解成若干条边不相交(点可以相交)的长度为2的链。
- 这是一个经典问题,参考POJ3222和CF858F
- 首先每个连通分量边必须为偶数
- 先随便找一棵 dfs 树,然后从深到浅考虑每一个点。找到所有和它相连的未被匹配的边,除了它连向父亲的边(这条边显然未被匹配)。如果这些边是偶数条,两两匹配即可,连向父亲的边会在处理父亲时被匹配上。如果这些边是奇数条,就把连向父亲的边也加入匹配。
- 时间复杂度: O ( n ) O(n) O(n)
#include<bits/stdc++.h>
#define int long long
using namespace std;
typedef pair<int,int> PII;
const int N=2e5+10;
int a[N],deg[N],dep[N];
bool vis[N];
vector<PII> adj[N],ans;
int bfs(int S){
queue<int> q;
q.push(S);
int res=0;
vis[S]=1;
while(!q.empty()){
auto u=q.front();
q.pop();
res+=deg[u];
for(auto [v,c]:adj[u]){
if(!vis[v]){
vis[v]=1;
q.push(v);
}
}
}
return res/2;
}
int dfs(int u,int fa,int from){
dep[u]=dep[fa]+1;
vector<int> vec;
for(auto [v,idx]:adj[u]){
if(v==fa)continue;
if(dep[v]>0){
//返祖边
if(dep[u]>dep[v])vec.push_back(idx);
}
else{
int t=dfs(v,u,idx);
if(t>0)vec.push_back(t);
}
}
while(vec.size()>1){
int x=vec.back();
vec.pop_back();
int y=vec.back();
vec.pop_back();
ans.emplace_back(x,y);
}
if(vec.size()==1){
ans.emplace_back(vec[0],from);
return -1;
}
return from;
}
void solve(){
int n;
cin>>n;
for(int i=0;i<=2*n;i++){
dep[i]=deg[i]=vis[i]=0;
adj[i].clear();
}
ans.clear();
map<int,int> idL,idR;
for(int i=1;i<=n;i++)cin>>a[i],idL[i-a[i]]=idR[i+a[i]]=0;
int cnt=0;
for(auto &[x,y]:idL)y=++cnt;
for(auto &[x,y]:idR)y=++cnt;
for(int i=1;i<=n;i++){
int x=idL[i-a[i]],y=idR[i+a[i]];
adj[x].emplace_back(y,i);
adj[y].emplace_back(x,i);
deg[x]++;
deg[y]++;
}
for(int i=1;i<=cnt;i++){
if(!vis[i]){
if(bfs(i)&1){
cout<<"No\n";
return;
}
dfs(i,0,-1);
}
}
cout<<"Yes\n";
for(auto [x,y]:ans)cout<<x<<" "<<y<<"\n";
}
signed main(){
cin.tie(0)->sync_with_stdio(0);
int T=1;
// freopen("in.txt","r",stdin);
// freopen("out.txt","w",stdout);
cin>>T;
while(T--)solve();
return 0;
}