【OI 档案-破碎的往事】云智
梦熊云智计划(2025、1 ~ 2025、6)
参与云智计划,目标:CSP-J AK,CSP-S 250+,NOIP 150+!
第一节-前缀和与差分,二分答案,双指针,整体二分
\(A\) 题:P4552 [Poetize6] IncDec Sequence
花了 \(1h\)……
既然标签里都写了差分那肯定是用差分的嘛,问题是思路想了半天发现不需要模拟计算过程……
如果序列的每个数都相等,那么它们的差分数组 \(c\) 满足除 \(c_1\) 外其余都为 \(1\),那么就要想办法把序列的差分数组变成这样。
事实上差分的区间加法/减法就能实现,差分的区间加减就是令 \(c_l+1,c_r-1\) 或 \(c_l-1,c_{r+1}\),那么,如果 \(c_l\) 与 \(c_{r+1}\) 为一正一负的情况下最好,这样相当与一次进行了两次操作,反之,就只进行了一次操作。
当时还想着直接模拟,没想到直接统计正数和与负数和就行了,因为差分数组第 \(i\) 位被修改不会影响其余位的差分数组。
那么答案 \(1\) 就为正数和与负数和的 max。
难么答案 \(2\) 呢,在全场只剩下正数或负数时,此时的答案分成了两个区间,就像样例一,可以选择增加或减少左区间,也可以选择右区间,因此共有正数和与负数和之差加一种结果,也就是答案 \(2\)。
代码如下:
#include<bits/stdc++.h>
using namespace std;
//#define int long long
#define rep(i,a,b) for(int i=a;i<=b;i++)
#define deap(i,a,b) for(int i=a;i>=b;i--)
#define in(a) a=read()
const int N = 1e5+5;
const int inf = INT_MAX;
inline int read() {
int x=0,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-48;
ch=getchar();
}
return x*f;
}
void fast() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
}
int a[N],c[N];
long long b[2];
signed main() {
//fast();
int n,p;
in(n);
rep(i,1,n)in(a[i]),c[i]=a[i]-a[i-1];
rep(i,2,n){
b[c[i]>0]+=c[i];
}
b[0]=-b[0];
if(b[0]<b[1])swap(b[0],b[1]);
cout<<b[0]<<'\n'<<b[0]-b[1]+1;
return 0;
}
\(B\) 题:U360489 灵茶八题 - 子数组 ^w+
考验二进制思想。
不难想到直接的 \(O(n^2)\) 的暴力,但是 TLE,所以需要进行优化(下文)。
暴力代码如下:
#include<bits/stdc++.h>
using namespace std;
//#define int long long
#define rep(i,a,b) for(int i=a;i<=b;i++)
#define deap(i,a,b) for(int i=a;i>=b;i--)
#define in(a) a=read()
const int N = 1e5+5;
const int inf = INT_MAX;
inline int read() {
int x=0,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-48;
ch=getchar();
}
return x*f;
}
void fast() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
}
int n,a[N],c[23],last;
signed main() {
//fast();
in(n);
rep(i,1,n)in(a[i]);
long long ans=0;
rep(i,1,n) {
int sum=a[i],h=a[i];
ans+=a[i];
rep(j,i+1,n) {
sum^=a[j];
ans+=sum;
h+=sum;
}
last=h;
}
cout<<ans;
return 0;
}
在暴力代码的基础上进行优化。
不难发现,这是一个前缀异或和,\(i=2\) 以后的都包含在 \(i=1\) 的计算中,而 \(i=1\) 的计算就是在算一个前缀和,用 \(c_i\) 表示。
而 \(c_i\) 与 \(c_{i-1}\) 的唯一差别就在于没有 \(a_{i-1}\) 这个数,因此它会影响后面的异或,但无伤大雅,因为只会影响 \(a_{i-1}\) 的二进制位为 \(1\) 的位的异或,也就是按位取反,因此可以从这点入手,进行优化。
在第一遍前缀和计算出 \(c_1\),并记录每一位 \(1\) 的个数,那么 \(c_i\) 的值就可以通过遍历二进制位的 \(1\) 的个数按上述方法按位异或,也就是原来的位 \(j\) 的 \(1\) 的个数变为 \(c_j-(n-i+1-c_j)\),就会减少 \([c_j-(n-i+1-c_j)]\times 2^j\),答案也就很好算了。
AC 代码如下
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define rep(i,a,b) for(int i=a;i<=b;i++)
#define deap(i,a,b) for(int i=a;i>=b;i--)
#define in(a) a=read()
const int N = 1e5+5;
const int inf = INT_MAX;
inline int read() {
int x=0,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-48;
ch=getchar();
}
return x*f;
}
void fast() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
}
int n,a[N],c[23],last;
signed main() {
//fast();
in(n);
rep(i,1,n)in(a[i]);
int ans=0;
int sum=0;
rep(i,1,n) {
sum^=a[i];
rep(j,0,21) {
c[j]+=bool(sum&(1<<j));
}
ans+=sum;
}
sum=ans;
rep(i,2,n) {
sum-=a[i-1];
rep(j,0,21) {
if(a[i-1]&(1<<j)) {
c[j]--;
sum-=((c[j]-(n-i+1-c[j])))*(1<<j);
c[j]=(n-i+1-c[j]);
}
}
ans+=sum;
}
cout<<ans;
return 0;
}
\(C\) 题:P9681 幽默的世界。
这道题暴力代码复杂的很高,而且直接枚举的指数达到了惊人的四次方!因此不能暴力。
事实上,一个幽默的序列 \([l,r]\) 就是满足 \([l,r-1]\) 的每一个数都小于等于 \(0\),且和加上 \(a_r\) 大于 \(0\),因此 \(a_r\) 一定大于 \(0\),可以从序列中的正数入手。
用一个数组 \(er\) 记录每个正数的下标,然后再遍历整个序列记录以 \(a_{er_i}\) 为 \(a_r\) 的答案。
可以先用一个前缀和实现区间求和,以判断 \([l,r-1]\) 的和加上 \(a_r\) 是否大于 \(0\),如果满足,用一个动态数组储存下来方便判断边界,同时增加以 \(a_{er_i}\) 为 \(a_r\) 的方案数。
计算完所有的答案后再用一个前缀和记录答案。
对于每次查询,用二分查找找到大于等于 \(l\) 的第一个正数的下标和小于等于 \(r\) 的第一个正数的下标,然后,由于大于等于 \(l\) 的第一个正数的可行方案的 \(l'\) 边界可能小于 \(l\) 因此需再进行一次二分查找,在动态数组里找大于等于 \(l\) 的 \(l'\),超出边界的不计入答案。然后求区间和即可。
代码实现
#include<bits/stdc++.h>
using namespace std;
//#define int long long
#define rep(i,a,b) for(int i=a;i<=b;i++)
#define deap(i,a,b) for(int i=a;i>=b;i--)
#define in(a) a=read()
const int N = 2e5+5;
const int inf = INT_MAX;
inline int read() {
int x=0,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-48;
ch=getchar();
}
return x*f;
}
void fast() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
}
int n,q,tot,a[N],er[N],ans[N],d[N];
long long f[N];
vector<int>v[N];
int low(int x){
int l=1,r=tot,Ans=1;
while(l<=r){
int mid=(l+r)>>1;
if(er[mid]<=x)Ans=mid,l=mid+1;
else r=mid-1;
}
return Ans;
}
signed main() {
//fast();
in(n),in(q);
rep(i,1,n) {
in(a[i]),f[i]=f[i-1]+a[i];
if(a[i]>0)er[++tot]=i;
}
rep(i,1,tot) {
rep(j,er[i-1]+1,er[i]) {
if(f[er[i]]-f[j-1]>0) {
v[er[i]].push_back(j);
ans[er[i]]++;
}
}
}
rep(i,1,tot)d[i]=d[i-1]+ans[er[i]];
while(q--) {
int l,r,h=0;
in(l),in(r);
int ls=lower_bound(er+1,er+tot+1,l)-er,rs=low(r);
auto anl=lower_bound(v[er[ls]].begin(),v[er[ls]].end(),l)-v[er[ls]].begin();
if(l<=er[ls]&&er[ls]<=r)
cout<<d[rs]-d[ls-1]-anl<<'\n';
else puts("0");
}
return 0;
}
\(D\) 题:[ABC203D] Pond
这道题怎么说呢,这题我第一眼想到的是:主席树求二维区间第 \(k\) 大……仔细一想的确是可行的,就是维护一个二维前缀权值线段树和,但……编码量有点……
还是考虑二分吧。
不难想到答案是单调的,但难点在于如何在 \(O(n^2)\) 的时间内判断一个数是否为矩阵内的中间数,其实只要求区间内小于等于这个数的个数就行了,用一个二维前缀和维护即可。
代码如下:
#include<bits/stdc++.h>
using namespace std;
//#define int long long
#define rep(i,a,b) for(int i=a;i<=b;i++)
#define deap(i,a,b) for(int i=a;i>=b;i--)
#define in(a) a=read()
const int N = 1e3+5;
const int inf = INT_MAX;
inline int read() {
int x=0,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-48;
ch=getchar();
}
return x*f;
}
void fast() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
}
int n,k,a[N][N],b[N][N];
int p;
bool check(int x) {
rep(i,1,n){
rep(j,1,n){
b[i][j]=b[i-1][j]+b[i][j-1]-b[i-1][j-1]+bool(a[i][j]<=x);
if(i>=k&&j>=k){
if(b[i][j]-b[i-k][j]-b[i][j-k]+b[i-k][j-k]>p)return true;
}
}
}
return false;
}
signed main() {
//fast();
in(n),in(k);
int l=0,r=INT_MIN;
p=((k*k)>>1)-(!((k*k)&1));
rep(i,1,n) {
rep(j,1,n) {
in(a[i][j]);
if(a[i][j]>r)r=a[i][j];
}
}
int ans=r;
while(l<=r) {
int mid=(l+r)>>1;
if(check(mid))ans=mid,r=mid-1;
else l=mid+1;
}
cout<<ans;
return 0;
}
\(E\) 题:[ABC203E] White Pawn
如果直接模拟肯定会炸,因为 \(N\) 的范围很大,因此还需进行离散化。在此并不推荐用 map,因为它的常数较大,而且本题以查询为主,因此更加推荐使用去重二分或哈希表。
思路与其他题解差不多,枚举每行的黑子,统计该行同列的黑子和左上或左下的黑子后再统一进行转移。如果在统计过程中就进行转移会影响后续的统计,因此需全部统计完后再进行状态转移,也就是完成该行的删添。
代码如下
#include<bits/stdc++.h>
using namespace std;
//#define int long long
#define rep(i,a,b) for(int i=a;i<=b;i++)
#define deap(i,a,b) for(int i=a;i>=b;i--)
#define in(a) a=read()
const int N = 2e5+5;
const int inf = INT_MAX;
inline int read() {
int x=0,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-48;
ch=getchar();
}
return x*f;
}
void fast() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
}
int n,m;
int a[N],b[N],c[N],top1,top2;
vector<int>sum[N];
set<int>s;//还是被迫用了红黑树
signed main() {
//fast();
in(n),in(m);
rep(i,1,m)in(a[i]),in(b[i]);
rep(i,1,m)c[i]=a[i];
sort(c+1,c+m+1);
int tot=unique(c+1,c+m+1)-c;//离散化
tot--;
rep(i,1,m)sum[lower_bound(c+1,c+tot+1,a[i])-c].push_back(b[i]);//处理数据
s.insert(n);//初始化
rep(i,1,tot) {//枚举每行的黑子
top1=top2=0;//清空统计数组
for(auto v:sum[i]) {//记录同列的黑子和左上或左下的黑子
if(s.find(v)!=s.end())a[++top1]=v;//不浪费一点空间!
if(s.find(v+1)!=s.end()||s.find(v-1)!=s.end())b[++top2]=v;
}
rep(j,1,top1)s.erase(a[j]);//删添
rep(j,1,top2)s.insert(b[j]);
}
cout<<s.size();
return 0;
}
中间的题跳过了……
\(H\) 题:[ABC153F] Silver Fox vs Monster
不难想到一个贪心思路,对每只怪兽按 \(x_i\) 升序排序,从左往右枚举每只怪兽,如果第 \(i\) 只怪兽的 \(h_i>0\),就以该怪兽的 \(x_i\) 为左边界用炸弹进行轰炸,直到消灭该怪兽为止,区间 \([x_i,x_i+2D]\) 内的怪兽也会受到对应的伤害,暴力减去即可。
TLE……
一个简单的 hack:
8 4 1
1 1
2 2
3 3
4 4
5 5
6 6
7 7
8 8
每一次只能消灭一只怪兽,因此时间复杂度为 \(O(n^2)\),不能接受。
优化思路
双指针优化。很明显,炸弹对怪兽的伤害是一个区间修改,这里就已经可以用树状数组直接实现,但其实用差分就足够了。那如何确定右边界?二分答案可以实现,但其实可以用双指针。这里的区间类似一个滑动窗口,因此可以用双指针确定右边界,然后通过差分实现区间修改,将时间复杂度优化到线性的 \(O(n)\)。
总复杂度为 \(O(n \log n)+O(n)=O(n \log n)\)。
代码实现
#include<bits/stdc++.h>
using namespace std;
#define rep(i,a,b) for(int i=a;i<=b;i++)
#define deap(i,a,b) for(int i=a;i>=b;i--)
#define in(a) a=read()
const int N = 2e5+5;
const int inf = INT_MAX;
inline long long read() {
long long x=0,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-48;
ch=getchar();
}
return x*f;
}
void fast() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
}
int n,d,a;
long long c[N];
pair<long long,long long>x[N];
signed main() {
//fast();
in(n),in(d),in(a);
rep(i,1,n)in(x[i].first),in(x[i].second);
sort(x+1,x+n+1);
long long j=1,t=0,ans=0;
rep(i,1,n){
t+=c[i];
while(j<=n&&x[j].first-x[i].first<=2*d+1)j++;
if(x[i].second-t>0){
int p=(x[i].second-t*a)/a+((x[i].second-t*a)%a);
t+=p*a;
c[j]-=p*a;
ans+=p;
}
}
cout<<ans;
return 0;
}
I 题:[ABC246D] 2-variable Function
这道题有着明显的单调性,可以直接用双指针解决,\(a\) 指针初始化为 \(1\) 从左往右逐一枚举,指针 \(b\) 初始化为 \(10^6\) 从右往左维护答案,然后暴力统计即可,时间复杂度是线性的 \(O(n)\)。
代码如下
#include<bits/stdc++.h>
using namespace std;
//#define int long long
#define rep(i,a,b) for(long long i=a;i<=b;i++)
#define deap(i,a,b) for(int i=a;i>=b;i--)
#define in(a) a=read()
const int N = 1e1;
const int inf = INT_MAX;
inline int read() {
int x=0,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-48;
ch=getchar();
}
return x*f;
}
void fast() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
}
long long solve(long long i,long long j){
return i*i*i+i*i*j+i*j*j+j*j*j;
}
signed main() {
//fast();
long long n;
cin>>n;
long long ans=0x3f3f3f3f3f3f3f3f;
long long j=1e6;
rep(i,0,1e6){
while(solve(i,j)>=n&&j>=0)j--;
j++;
if(solve(i,j)>=n)
ans=min(ans,solve(i,j));
}
cout<<ans;
return 0;
}
中间的题又跳了……
\(K\) 题:P3834 【模板】可持久化线段树 2
这东东老师要求用整体二分,但整体二分那树状数组维护的那段我每听懂……所以主席树秒了……
代码如下
还是喜欢链式风格的主席树算法啊。
#include<bits/stdc++.h>
using namespace std;
//#define int long long
#define rep(i,a,b) for(int i=a;i<=b;i++)
#define deap(i,a,b) for(int i=a;i>=b;i--)
#define in(a) a=read()
const int N = 2e5 + 5;
const int inf = INT_MAX;
inline int read() {
int x=0,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-48;
ch=getchar();
}
return x*f;
}
void fast() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
}
struct Tree{
int l,r,v;
};
vector<Tree>t;
int root[N];
int n,m,a[N],b[N];
int change(int idx,int l,int r,int q,int val){
Tree tmp=t[idx];
if(l==r){
tmp.v+=val;
t.push_back(tmp);
return t.size()-1;
}
int mid=(l+r)>>1;
if(q<=mid){
tmp.l=change(tmp.l,l,mid,q,val);
}else{
tmp.r=change(tmp.r,mid+1,r,q,val);
}
tmp.v=t[tmp.l].v+t[tmp.r].v;
t.push_back(tmp);
return t.size()-1;
}
int query(int idxl,int idxr,int l,int r,int q){
if(l==r){
return l;
}
int mid=(l+r)>>1,tmp=t[t[idxr].l].v-t[t[idxl].l].v;
if(q<=tmp)return query(t[idxl].l,t[idxr].l,l,mid,q);
else return query(t[idxl].r,t[idxr].r,mid+1,r,q-tmp);
}
signed main() {
//fast();
in(n),in(m);
rep(i,1,n)a[i]=b[i]=read();
sort(b+1,b+n+1);
int tot=unique(b+1,b+n+1)-b-1;
Tree tmp;
tmp.l=tmp.r=tmp.v=0;
t.push_back(tmp);
rep(i,1,n){
a[i]=lower_bound(b+1,b+tot+1,a[i])-b;
root[i]=change(root[i-1],1,tot,a[i],1);
}
rep(i,1,m){
int l,r,k;
in(l),in(r),in(k);
printf("%d\n",b[query(root[l-1],root[r],1,tot,k)]);
}
return 0;
}
第二节-位运算,快速莫比乌斯/沃尔什变换 (FMT/FWT),高维前缀和
忠于期末考试,结果一题没写……
第三节-贪心思想,反悔贪心,模拟费用流,Slope Trick,分治思想,猫树
-
学习情况:
在理论知识上除了 Slope Trick 以外都基本掌握。
AB题好久以前就写了,因此不做记录。
\(C\) 题:P4053 [JSOI2007] 建筑抢修
线性时间安排问题板题。
一个经典的贪心思路:首先对数据按截止时间从小到大排序,然后逐一枚举,不断拓展目前截止时间和更新目前剩余时间,若时间够,直接完成;若不够,将其所需时间与曾经完成的事件中用时最大者做比较,如果用时小于它,直接替换。
代码如下
#include<bits/stdc++.h>
using namespace std;
//#define int long long
#define rep(i,a,b) for(int i=a;i<=b;i++)
#define deap(i,a,b) for(int i=a;i>=b;i--)
#define in(a) a=read()
const int N = 1e5+5e4+5;
const int inf = INT_MAX;
inline int read() {
int x=0,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-48;
ch=getchar();
}
return x*f;
}
void fast() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
}
struct node{
int t,s;
}a[N];
int n,ans,tmp,now;
bool cmp(node x,node y){
return x.s<y.s;
}
priority_queue<int>q;
signed main() {
in(n);
rep(i,1,n)in(a[i].t),in(a[i].s);
sort(a+1,a+n+1,cmp);
rep(i,1,n){
now+=a[i].s-tmp;
tmp=a[i].s;
if(now>=a[i].t){
now-=a[i].t;
q.push(a[i].t);
}else if(!q.empty()){
int t=q.top();
if(a[i].t<t){
q.pop();
q.push(a[i].t);
now=now+t-a[i].t;
}
}
}
cout<<q.size();
return 0;
}
\(D\) 题:P2587 [ZJOI2008] 泡泡堂
逆天贪心,被田忌赛马的烂思路卡了好久,难崩。
直接做四个分类讨论进行贪心转移:
- 1、若我方最弱能胜过敌方最弱,就此安排。
- 2、若我方最强能胜过敌方最强,就此安排。
- 3、若我方最弱能与敌方最强打成平手,就此安排
- 4、反之,让我方最弱“送”给敌方最强。
代码实现
#include<bits/stdc++.h>
using namespace std;
//#define int long long
#define rep(i,a,b) for(int i=a;i<=b;i++)
#define deap(i,a,b) for(int i=a;i>=b;i--)
#define in(a) a=read()
const int N = 1e5+5;
const int inf = INT_MAX;
inline int read() {
int x=0,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-48;
ch=getchar();
}
return x*f;
}
void fast() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
}
signed main() {
//fast();
int n;
in(n);
int *a=new int[n+1],*b=new int[n+1];
rep(i,1,n)in(a[i]);
rep(i,1,n)in(b[i]);
sort(a+1,a+n+1);
sort(b+1,b+n+1);
int l1=1,l2=1,r1=n,r2=n,ans=0;
while(l1<=r1&&l2<=r2){
if(a[l1]>b[l2])ans+=2,l1++,l2++;
else if(a[r1]>b[r2])ans+=2,r1--,r2--;
else if(a[l1]==b[r2])ans++,l1++,r2--;
else l1++,r2--;
}
cout<<ans<<' ';
l1=1,l2=1,r1=n,r2=n,ans=0;
while(l1<=r1&&l2<=r2){
if(a[l1]<b[l2])ans+=2,l1++,l2++;
else if(a[r1]<b[r2])ans+=2,r1--,r2--;
else if(a[r1]==b[l2])ans++,l2++,r1--;
else l2++,r1--;
}
cout<<n*2-ans<<' ';
delete[]a;
delete[]b;
return 0;
}
\(I\) 题:P4597 序列 sequence
看老师的思路写的,虽然老师的证明没看懂……
#include<bits/stdc++.h>
using namespace std;
//#define int long long
#define rep(i,a,b) for(long long i=a;i<=b;i++)
#define deap(i,a,b) for(int i=a;i>=b;i--)
#define in(a) a=read()
const int N = 1e1;
const int inf = INT_MAX;
inline int read() {
int x=0,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-48;
ch=getchar();
}
return x*f;
}
void fast() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
}
signed main() {
//fast();
int n;
in(n);
priority_queue<int>q;
long long ans=0;
rep(i,1,n){
int x;
in(x);
q.push(x);
if(x<q.top()){
ans=ans+q.top()-x;
q.pop();
q.push(x);
}
}
cout<<ans;
return 0;
}
第四节-并查集及其技巧(种类并查集,带权并查集,可撤销并查集,扩展域并查集,启发式合并,按秩合并)
\(A\) 题:P3367 【模板】并查集
板题,切掉了。
清朝代码如下:
#include<bits/stdc++.h>
using namespace std;
int sets[1000000];
int find(int x){
if(sets[x]!=x)sets[x]=find(sets[x]);
return sets[x];
}
void unoin(int &x,int &y){
x=find(x),y=find(y);
sets[x]=y;
}
bool nouoi(int x,int y){
if(find(x)==find(y))return true;
else return false;
}
int main() {
int q,m;
cin>>q>>m;
for(int i=1;i<=q;i++)sets[i]=i;
for(int i=0;i<m;i++){
int x,y,z;
cin>>x>>y>>z;
if(x==1)unoin(y,z);
else {
if(nouoi(y,z))cout<<"Y\n";
else cout<<"N\n";
}
}
return 0;
}
\(B\) 题:P1955 [NOI2015] 程序自动分析
一点思维罢了。
清朝代码如下:
#include <bits/stdc++.h>
using namespace std;
const int maxn=3E6+5;
const int mod=2999999;
int t;
int n,cnt;
struct st {
int x,y,k;
} a[maxn];
vector<pair<int,int> > mp[maxn];
void Insert(int x) {
for(int i=0; i<mp[x%mod].size(); i++) {
if(mp[x%mod][i].first==x)return ;
}
mp[x%mod].push_back(make_pair(x,++cnt));
}
int Hash(int x) {
for(int i=0; i<mp[x%mod].size(); i++) {
if(mp[x%mod][i].first==x) {
return mp[x%mod][i].second;
}
}
}
int f[maxn];
int Find(int x) {
if(x==f[x])return x;
return f[x]=Find(f[x]);
}
void Merge(int x,int y) {
x=Find(x);
y=Find(y);
if(x==y)return ;
f[x]=y;
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin>>t;
while(t--) {
bool flag=0;
for(int i=0; i<mod; i++)mp[i].clear();
cnt=0;
cin>>n;
for(int i=1; i<=n; i++) {
cin>>a[i].x>>a[i].y>>a[i].k;
Insert(a[i].x);
Insert(a[i].y);
}
for(int i=1; i<=cnt; i++)f[i]=i;
for(int i=1; i<=n; i++) {
int x,y,k;
x=Hash(a[i].x);
y=Hash(a[i].y);
k=a[i].k;
if(k==1) {
Merge(x,y);
}
}
for(int i=1; i<=n; i++) {
int x,y,k;
x=Hash(a[i].x);
y=Hash(a[i].y);
k=a[i].k;
if(k==0) {
if(Find(x)==Find(y)) {
flag=1;
break;
}
}
}
if(flag==1) {
cout<<"NO\n";
} else {
cout<<"YES\n";
}
}
return 0;
}
\(C\) 题:P1197 [JSOI2008] 星球大战
运用离线算法倒序维护并查集,这题我写过类似的,像关闭农场之类的。
本来可以切掉的,却被卡到凌晨一点,结果无赖对拍时发现是自己范围开小了,难崩。
现代代码如下
#include<bits/stdc++.h>
using namespace std;
//#define int long long
#define I_love_Foccarus return
#define rep(i,a,b) for(int i=a;i<=b;i++)
#define deap(i,a,b) for(int i=a;i>=b;i--)
#define in(a) a=read()
const int N = 2e6+5;
const int inf = INT_MAX;
inline int read() {
int x=0,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-48;
ch=getchar();
}
return x*f;
}
void fast() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
}
struct Edge {
int act,nex;
} edge[N<<2];
int head[N],eid;
void eadd(int u,int v) {
eid++;
edge[eid].act=v,edge[eid].nex=head[u];
head[u]=eid;
}
int fa[N],t[N],st[N],out[N],otop;
int find(int u) {
return fa[u]==u?u:fa[u]=find(fa[u]);
}
int ans;
void dfs(int u) {
for(int i=head[u]; i; i=edge[i].nex) {
int v=edge[i].act;
if(!t[v]&&find(u)!=find(v)) {
fa[find(u)]=find(v);
ans--;
dfs(v);
}
}
}
int k;
int n,m;
signed main() {
//fast();
in(n),in(m);
n--;
rep(i,0,n)fa[i]=i;
rep(i,1,m) {
int x,y;
in(x),in(y);
eadd(x,y),eadd(y,x);
}
in(k);
rep(i,1,k) {
in(st[i]);
t[st[i]]=1;
}
ans=n-k+1;
rep(i,0,n) {
if(!t[i])dfs(i);
}
out[++otop]=ans;
deap(j,k,1) {
ans++,t[st[j]]=0;
for(int i=head[st[j]]; i; i=edge[i].nex) {
int v=edge[i].act;
if(!t[v]&&find(st[j])!=find(v)) {
fa[find(st[j])]=find(v);
ans--;
}
}
head[st[j]]=0;
out[++otop]=ans;
}
while(otop) {
printf("%d\n",out[otop--]);
}
return 0;
}
\(D\) 题:P2024 [NOI2001] 食物链
种类并查集板题,切了。
现代代码如下
#include<bits/stdc++.h>
using namespace std;
//#define int long long
#define rep(i,a,b) for(int i=a;i<=b;i++)
#define deap(i,a,b) for(int i=a;i>=b;i--)
#define in(a) a=read()
const int N = 2e6+5;
const int inf = INT_MAX;
inline int read() {
int x=0,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-48;
ch=getchar();
}
return x*f;
}
void fast() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
}
int fa[N];
int find(int u) {
return fa[u]==u?u:fa[u]=find(fa[u]);
}
signed main() {
//fast();
int n,k,ans=0;
in(n),in(k);
rep(i,0,n*3) fa[i]=i;
rep(i,1,k) {
int op,x,y;
in(op),in(x),in(y);
if (x>n||y>n) {
ans++;
} else if(op==1) {
if(find(n+x)==find(y)||find(n+y)==find(x)) {
ans++;
} else {
fa[find(x)]=find(y);
fa[find(n+x)]=find(n+y);
fa[find((n<<1)+x)]=find((n<<1)+y);
}
} else {
if (find(x)==find(y)||find(x)==find(n+y)) {
ans++;
} else {
fa[find(n+x)]=find(y);
fa[find((n<<1)+x)]=find(n+y);
fa[find(x)]=find((n<<1)+y);
}
}
}
cout<<ans;
return 0;
}
\(E\) 题:P1196 [NOI2002] 银河英雄传说
带权并查集板题,切了。
清朝代码如下
#include<bits/stdc++.h>
using namespace std;
int s[30005];
int d[30005];
int zs[30005];
int find(int i) {
if(s[i]!=i) {
int root=find(s[i]);
d[i]+=d[s[i]];
s[i]=root;
return root;
}
return s[i];
}
void merge(int x,int y) {
int fa=find(x),fb=find(y);
d[fa]+=zs[fb];
s[fa]=fb;
zs[fb]+=zs[fa];
zs[fa]=0;
}
bool access(int x,int y){
if(find(x)==find(y))return true;
else return false;
}
int inquire(int x,int y) {
if(!access(x,y))return -1;
else return abs(d[x]-d[y])-1;
}
signed main() {
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
int n=30000,m;
cin>>m;
for(int i=1; i<=n; i++)s[i]=i,d[i]=0,zs[i]=1;
for(int i=0; i<m; i++) {
char x;
cin>>x;
if(x=='M') {
int a,b;
cin>>a>>b;
merge(a,b);
} else {
int a,b;
cin>>a>>b;
cout<<inquire(a,b)<<'\n';
}
}
return 0;
}
\(F\) 题:Destroying Array
采用离线算法,从后往前回复被删除的点,最大连续和可以用带权并查集维护。
现代代码如下
#include<iostream>
#include<cstring>
using namespace std;
#define rep(i,a,b) for(int i=a;i<=b;i++)
#define deap(i,a,b) for(int i=a;i>=b;i--)
#define in(a) a=read()
const int N = 1e5+5;
const int inf = INT_MAX;
inline int read() {
int x=0,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-48;
ch=getchar();
}
return x*f;
}
void fast() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
}
int fa[N];
long long ans[N],t[N];
int find(int u) {
return fa[u]==u?u:fa[u]=find(fa[u]);
}
signed main() {
//fast();
int n;
in(n);
int *a=new int[n+1],*b=new int[n+1];
rep(i,1,n)in(a[i]);
rep(i,1,n)in(b[i]);
rep(i,1,n)fa[i]=i;
ans[n]=0;
memset(t,-1,sizeof(t));
deap(i,n-1,1) {
t[b[i+1]]=a[b[i+1]];
if(b[i+1]-1>0&&~t[b[i+1]-1]) {
t[b[i+1]]+=t[find(b[i+1]-1)];
fa[find(b[i+1]-1)]=find(b[i+1]);
}
if(b[i+1]+1<=n&&~t[b[i+1]+1]) {
t[b[i+1]]+=t[find(b[i+1]+1)];
fa[find(b[i+1]+1)]=find(b[i+1]);
}
ans[i]=max(ans[i+1],t[b[i+1]]);
}
rep(i,1,n)printf("%lld\n",ans[i]);
return 0;
}
\(G\) 题:P3402 可持久化并查集
可持久化并查集板题,某人因为此题再度熬到凌晨一点。可持久化并查集可以通过可持久化数组实现,这也就要使用我们伟大的主席树算法了。
我快速的写完了代码,交了上去,结果——常数被卡了。
考虑优化常数。按秩合并?摆烂,直接随机化吧。
舍伍德算法启动。——依旧被卡。
启发式合并——被卡依旧。
错误的按深度合并——死循环。
现在是北京时间一点一十二分~
\bx 什么意思啊?GTP 改错……
越改越错……
受不了了!睡觉!
刷牙中……
等等,我的一个下标貌似多减了一个1?!!
还真是,AC——qnq。
代码如下:
#include<bits/stdc++.h>
using namespace std;
//#define int long long
#define I_love_Foccarus return
#define rep(i,a,b) for(int i=a;i<=b;i++)
#define deap(i,a,b) for(int i=a;i>=b;i--)
#define in(a) a=read()
const int N = 1e6+5;
const int inf = INT_MAX;
inline int read() {
int x=0,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-48;
ch=getchar();
}
return x*f;
}
void fast() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
}
int tot;
struct Tree {
int l,r,v,sz;
};
Tree t[N<<5];
inline void push_back(Tree x) {
t[++tot]=x;
}
int root[N];
int n,m,a[N],b[N];
int js;
inline int change(int idx,int l,int r,int q,int be) {
Tree tmp=t[idx];
if(l==r) {
tmp.v=be;
push_back(tmp);
return tot;
}
int mid=(l+r)>>1;
if(q<=mid) {
tmp.l=change(tmp.l,l,mid,q,be);
} else {
tmp.r=change(tmp.r,mid+1,r,q,be);
}
push_back(tmp);
return tot;
}
inline int add(int idx,int l,int r,int q) {
Tree tmp=t[idx];
if(l==r) {
tmp.sz++;
push_back(tmp);
return tot;
}
int mid=(l+r)>>1;
if(q<=mid) {
tmp.l=add(tmp.l,l,mid,q);
} else {
tmp.r=add(tmp.r,mid+1,r,q);
}
push_back(tmp);
return tot;
}
inline int query(int idx,int l,int r,int q) {
if(l==r) {
return idx;
}
int mid=(l+r)>>1;
if(q<=mid)return query(t[idx].l,l,mid,q);
else return query(t[idx].r,mid+1,r,q);
}
inline int build(int l,int r) {
Tree tmp;
if(l==r) {
tmp.l=tmp.r=-1,tmp.v=l,tmp.sz=0;
push_back(tmp);
return tot;
}
int mid=(l+r)>>1;
tmp.l=build(l,mid),tmp.r=build(mid+1,r);
tmp.v=tmp.sz=-1;
push_back(tmp);
return tot;
}
int anser;
inline int find(int qt,int u) {
int v=query(root[qt],1,n,u);
if(u==t[v].v)return v;
else return find(qt,t[v].v);
}
signed main() {
//fast();
in(n),in(m);
root[0]=build(1,n);
rep(i,1,m) {
int op,qx,qy,val,l,r;
in(op),in(qx);
if(op==1) {
root[i]=root[i-1];
in(qy);
int fx=find(i,qx),fy=find(i,qy);
if(t[fx].v!=t[fy].v) {
if(t[fx].sz>t[fy].sz)swap(fx,fy);
root[i]=change(root[i-1],1,n,t[fx].v,t[fy].v);
if(t[fx].sz==t[fy].sz)
root[i]=add(root[i],1,n,t[fy].v);
}
} else if(op==2) {
root[i]=root[qx];
} else {
in(qy);
cout<<(find(i-1,qx)==find(i-1,qy))<<'\n';
root[i]=root[i-1];
}
}
return 0;
}
第五节-单调栈,单调队列,哈希表
\(A\) 题:P5788 【模板】单调栈
板题,切了。
清朝代码如下:
#include<bits/stdc++.h>
using namespace std;
//#define int long long
#define rep(i,a,b) for(int i=a;i<=b;i++)
const int maxn=INT_MAX;
const double INF=DBL_MAX;
inline int read() {
int x=0,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-48;
ch=getchar();
}
return x*f;
}
inline void write(int x){
if(x>9)write(x/10);
putchar('0'+(x%10));
}
signed main() {
int n;
n=read();
int *a=new int[n+1],*b=new int[n+1];
stack<int>s;
rep(i,1,n)a[i]=read();
s.push(1);
rep(i,2,n) {
while(!s.empty()&&a[s.top()]<a[i])b[s.top()]=i,s.pop();
s.push(i);
}
rep(i,1,n)write(b[i]),putchar(' ');
return 0;
}
\(B\) 题:Imbalanced Array
题意转换:$$\sum_{i=1}^{n} \sum_{j=i}^{n} (\max_{i\le k\le j} a_k-\min_{i\le k\le j} a_k)$$
就是把所有连续子段的最大值减去所有连续子段的最小值,因此可以拆开来统计。对于每一个 \(a_i\),可以计算出以 \(a_i\) 为最大值/最小值的最大区间,然后在最后统计时拿这些区间长度乘上 \(a_i\) 就是这些区间的最终结果,最大值加上,最小值减去。对于求最大值/最小值的最大区间,可以用一个单调栈维护,时间复杂度为 \(O(n)\)。
代码如下:
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define rep(i,a,b) for(int i=a;i<=b;i++)
#define deap(i,a,b) for(int i=a;i>=b;i--)
#define in(a) a=read()
const int N = 2e6+5;
const int inf = INT_MAX;
inline int read() {
int x=0,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-48;
ch=getchar();
}
return x*f;
}
void fast() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
}
int sta1[N],sta2[N],top1,top2,l[N],r[N],ls[N],rs[N];
signed main() {
//fast();
int n;
in(n);
int *a=new int[n+1];
rep(i,1,n)in(a[i]);
int ans=0,sum;
rep(i,1,n){
while(top1&&a[sta1[top1]]<a[i])r[sta1[top1--]]=i-1;
while(top2&&a[sta2[top2]]>a[i])rs[sta2[top2--]]=i-1;
sta1[++top1]=sta2[++top2]=i;
}
while(top1)r[sta1[top1--]]=n;
while(top2)rs[sta2[top2--]]=n;
deap(i,n,1){
while(top1&&a[sta1[top1]]<=a[i])l[sta1[top1--]]=i+1;
while(top2&&a[sta2[top2]]>=a[i])ls[sta2[top2--]]=i+1;
sta1[++top1]=sta2[++top2]=i;
}
while(top1)l[sta1[top1--]]=1;
while(top2)ls[sta2[top2--]]=1;
rep(i,1,n){
ans=ans+a[i]*(i-l[i]+1)*(r[i]-i+1);
ans=ans-a[i]*(i-ls[i]+1)*(rs[i]-i+1);
}
cout<<ans;
delete[]a;
return 0;
}
\(C\) 题:P2422 良好的感觉
这不和 \(B\) 题一样?求出以第 \(i\) 为最小值的区间范围,然后直接让这个范围内的和于之相乘即可。单调栈与前缀和能够将复杂度优化至 \(O(n)\)级别。
代码如下:
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define rep(i,a,b) for(int i=a;i<=b;i++)
#define deap(i,a,b) for(int i=a;i>=b;i--)
#define in(a) a=read()
const int N = 2e6+5;
const int inf = INT_MAX;
inline int read() {
int x=0,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-48;
ch=getchar();
}
return x*f;
}
void fast() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
}
int sta[N],top,l[N],r[N],c[N];
signed main() {
//fast();
int n;
in(n);
int *a=new int[n+1];
rep(i,1,n)in(a[i]),c[i]=c[i-1]+a[i];
int ans=0,sum;
rep(i,1,n){
while(top&&a[sta[top]]>a[i])r[sta[top--]]=i-1;
sta[++top]=i;
}
while(top)r[sta[top--]]=n;
deap(i,n,1){
while(top&&a[sta[top]]>=a[i])l[sta[top--]]=i+1;
sta[++top]=i;
}
while(top)l[sta[top--]]=1;
rep(i,1,n){
ans=max(ans,(c[r[i]]-c[l[i]-1])*a[i]);
}
cout<<ans;
delete[]a;
return 0;
}
\(D\) 题:P4147 玉蟾宫
题目给出的矩阵内的若干个子矩阵,在满足同列的情况下,可以用单调栈维护和统计子矩阵最大面积,需要预处理每个数据的行左右边界,也就是子矩阵的宽,然后我们通过枚举每一列就可以得出答案。
代码如下:
#include<bits/stdc++.h>
using namespace std;
//#define int long long
#define I_love_Foccarus return
#define rep(i,a,b) for(int i=a;i<=b;i++)
#define deap(i,a,b) for(int i=a;i>=b;i--)
#define in(a) a=read()
const int N = 1e3+5;
const int inf = INT_MAX;
inline int read() {
int x=0,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-48;
ch=getchar();
}
return x*f;
}
void fast() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
}
int n,m,f[N][N],ans;
char a[N][N],b[N][N];
int s[N],h[N],top;
signed main() {
fast();
cin>>n>>m;
rep(i,1,n)rep(j,1,m)cin>>a[i][j];
rep(i,1,n)rep(j,1,m) if(a[i][j]=='F')f[i][j]=f[i][j-1]+1;
rep(j,1,m) {
top=1;
s[top]=1;
h[top]=f[1][j];
int sum=0;
rep(i,2,n) {
int l=0;
while(top&&h[top]>=f[i][j]) {
l+=s[top];
sum=max(sum,h[top]*l);
top--;
}
s[++top]=l+1,h[top]=f[i][j];
}
int l=0;
while(top) {
l+=s[top];
sum=max(sum,h[top]*l);
top--;
}
ans=max(ans,sum);
}
cout<<ans*3;
return 0;
}
\(E\) 题:P1578 奶牛浴场
虽然此题和 \(D\) 题很像,但不允许 \(O(WL)\) 的暴力复杂度,需要从障碍点入手,因为障碍点的数量满足 \(O(n^2)\) 的时间复杂度。
思路很简单,枚举每个障碍点作为矩阵的一点,维护其长宽的正确性,满足矩阵内不存在障碍点,对每个矩阵取 \(\max\) 就行了。对数据排好序,把原矩阵的四个顶点带上,然后直接爆力扫一遍就可以了。
代码如下:
#include<bits/stdc++.h>
using namespace std;
//#define int long long
#define I_love_Foccarus return
#define rep(i,a,b) for(int i=a;i<=b;i++)
#define deap(i,a,b) for(int i=a;i>=b;i--)
#define in(a) a=read()
#define fi first
#define se second
const int N = 3e4+5;
const int inf = INT_MAX;
inline int read() {
int x=0,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-48;
ch=getchar();
}
return x*f;
}
void fast() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
}
int n,m,k,b[N],tot,ans;
pair<int,int> a[N];
int minx[N],maxx[N];
signed main() {
in(n),in(m),in(k);
rep(i,1,k) {
int x,y;
in(x),in(y);
a[i]=make_pair(x,y);b[++tot]=y;
}
a[++k]=make_pair(0,m),a[++k]=make_pair(n,0),a[++k]=make_pair(0,0),a[++k]=make_pair(n,m);
b[++tot]=0,b[++tot]=m;
sort(a+1,a+k+1);
sort(b+1,b+tot+1);
tot=unique(b+1,b+tot+1)-b-1;
rep(i,2,tot)ans=max(ans,n*(b[i]-b[i-1]));
rep(i,1,k){
int maxx=0,minx=m,y;
rep(j,i+1,k){
y=a[j].fi-a[i].fi;
ans=max(ans,(minx-maxx)*y);
if(a[j].se>a[i].se)minx=min(minx,a[j].se);
else maxx=max(maxx,a[j].se);
}
}
cout<<ans;
return 0;
}
\(F\) 题:P3400 仓鼠窝
思路:通过单调栈求出每行每列的左右边界,维护高度的单调性,在最后统计左右边界与高度的积的和即可。
为啥单调栈求左右边界的题目这么多啊?
代码如下:
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define rep(i,a,b) for(int i=a;i<=b;i++)
#define deap(i,a,b) for(int i=a;i>=b;i--)
#define in(a) a=read()
const int N = 3e3+5;
const int inf = INT_MAX;
inline int read() {
int x=0,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-48;
ch=getchar();
}
return x*f;
}
void fast() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
}
int a[N][N],h[N],top,sta[N],l[N],r[N];
signed main() {
//fast();
int n,m;
in(n),in(m);
rep(i,1,n)rep(j,1,m)in(a[i][j]);
int ans=0,sum;
rep(i,1,n) {
rep(j,1,m)h[j]=a[i][j]==0?0:h[j]+1;
rep(j,1,m) {
while(top&&h[sta[top]]>h[j])r[sta[top--]]=j-1;
sta[++top]=j;
}
while(top)r[sta[top--]]=m;
deap(j,m,1) {
while(top&&h[sta[top]]>=h[j])l[sta[top--]]=j+1;
sta[++top]=j;
}
while(top)l[sta[top--]]=1;
rep(j,1,m) {
ans+=(j-l[j]+1)*(r[j]-j+1)*h[j];
}
}
cout<<ans;
return 0;
}
\(H\) 题:P1886 滑动窗口 /【模板】单调队列
板题,切了。
清朝代码如下:
#include<bits/stdc++.h>
using namespace std;
int n,k;
deque<int>q;
int main() {
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin>>n>>k;
int *a=new int[n+1];
for(int i=1; i<=n; i++)cin>>a[i];
for(int i=1; i<=n; i++) {
while(!q.empty()&&a[q.back()]>a[i])q.pop_back();
q.push_back(i);
while(!q.empty()&&q.front()<=i-k)q.pop_front();
if(i>=k)cout<<a[q.front()]<<' ';
}
while(!q.empty())q.pop_front();
cout<<'\n';
for(int i=1; i<=n; i++) {
while(!q.empty()&&a[q.back()]<a[i])q.pop_back();
q.push_back(i);
while(!q.empty()&&q.front()<=i-k)q.pop_front();
if(i>=k)cout<<a[q.front()]<<' ';
}
}
\(I\) 题:P3088 [USACO13NOV] Crowded Cows S
单调队列维护区间最大值,从左往右枚举 \(i\),如果 \([x_i-D,x_i]\) 中的最大值是 \(h_i\) 的两倍,打一个 vis 标记。然后再从右往左枚举 \(i\),如果 \([x_i,x_i+D]\) 中的最大值是 \(h_i\) 的两倍,打再一个 vis 标记。然后进行统计,答案为打了两个标记的总数。
代码如下:
#include<bits/stdc++.h>
using namespace std;
//#define int long long
#define I_love_Foccarus return
#define rep(i,a,b) for(int i=a;i<=b;i++)
#define deap(i,a,b) for(int i=a;i>=b;i--)
#define in(a) a=read()
const int N = 5e4+5;
const int inf = INT_MAX;
inline int read() {
int x=0,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-48;
ch=getchar();
}
return x*f;
}
void fast() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
}
struct node {
int x , h;
} a[N];
int n , d , ans , vis[N];
bool cmp(node x,node y) {
return x.x < y.x;
}
deque<int>q;
signed main() {
//fast();
in(n),in(d);
for(int i = 1 ; i <= n ; i++)in(a[i].x),in(a[i].h);
sort(a + 1 , a + n + 1 , cmp);
for(int i = 1 ; i <= n ; i++) {
while(!q.empty() && a[q.front()].x + d < a[i].x)q.pop_front();
while(!q.empty() && a[q.back()].h < a[i].h)q.pop_back();
if(!q.empty() && a[q.front()].h >= a[i].h * 2) vis[i]++;
q.push_back(i);
}
while(!q.empty())q.pop_back();
for(int i = n ; i > 0 ; i--) {
while(!q.empty() && a[q.front()].x - d > a[i].x)q.pop_front();
while(!q.empty() && a[q.back()].h < a[i].h)q.pop_back();
if(!q.empty() && a[q.front()].h >= a[i].h * 2) vis[i]++;
q.push_back(i);
}
for(int i = 1 ; i <= n ; i++) ans += vis[i] == 2;
cout<<ans;
return 0;
}
\(J\) 题:P2629 好消息,坏消息
这题?不线段树吗?
维护一个长度为 \(2n\) 的区间前缀和,用两个指针 \(l,r\) 模拟一个长度为 \(n\) 的“滑动窗口”,然后强行模拟转换过程,区间右移时相当于删除原区间左边界的数 \(a_l\),由于我们维护的是前缀和,所以需要区间修改,直接对整个区间 \([l,r]\) 减去 \(a_l\) 即可,然后要拓展右边界,就是用 \([l+1,r]\) 的和加上 \(a_{r-n+1}\),直接对 \([r+1,r+1]\) 单点修改即可,然后直接求区间 \([l,r]\) 的区间最小值,如果其大于 \(0\),更新答案。
代码如下(TLE):
#include<bits/stdc++.h>
using namespace std;
#define rep(i,a,b) for(int i=a;i<=b;i++)
#define deap(i,a,b) for(int i=a;i>=b;i--)
#define in(a) a=read()
const int N = 2e6+5;
const int inf = INT_MAX;
inline long long read() {
long long x=0,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-48;
ch=getchar();
}
return x*f;
}
void fast() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
}
int c[N];
struct linetree{
int cl,cr,ans,lazy;
};
vector<linetree>t;
int build(int l,int r){
linetree tmp;
if(l==r){
tmp.cl=tmp.cr=-1;
tmp.ans=c[l];
tmp.lazy=0;
t.push_back(tmp);
return t.size()-1;
}
int mid=(l+r)>>1;
tmp.cl=build(l,mid);
tmp.cr=build(mid+1,r);
tmp.ans=min(t[tmp.cl].ans,t[tmp.cr].ans);
tmp.lazy=0;
t.push_back(tmp);
return t.size()-1;
}
void push_down(int idx){
t[t[idx].cl].ans+=t[idx].lazy;
t[t[idx].cr].ans+=t[idx].lazy;
t[t[idx].cl].lazy+=t[idx].lazy;
t[t[idx].cr].lazy+=t[idx].lazy;
t[idx].lazy=0;
}
void update(int l,int r,int ql,int qr,int idx,int val){
if(qr<l||r<ql)return;
if(ql<=l&&r<=qr){
t[idx].ans+=val;
t[idx].lazy+=val;
return;
}
int mid=(l+r)>>1;
push_down(idx);
update(l,mid,ql,qr,t[idx].cl,val);
update(mid+1,r,ql,qr,t[idx].cr,val);
t[idx].ans=min(t[t[idx].cl].ans,t[t[idx].cr].ans);
}
int query(int l,int r,int ql,int qr,int idx){
if(qr<l||r<ql)return inf;
if(ql<=l&&r<=qr){
return t[idx].ans;
}
int mid=(l+r)>>1;
push_down(idx);
return min(query(l,mid,ql,qr,t[idx].cl),query(mid+1,r,ql,qr,t[idx].cr));
t[idx].ans=min(t[t[idx].cl].ans,t[t[idx].cr].ans);
}
int a[N];
signed main() {
//fast();
int n,p=1,ans=0,sum;
in(n);
rep(i,1,n)in(a[i]);
rep(i,1,n){
c[i]=c[i-1]+a[i];
}
sum=c[n];
int root=build(1,2*n);
int l=1,r=n;
while(l<=n){
if(query(1,2*n,l,r,root)>=0)ans++;
update(1,2*n,l,r,root,-a[l]);
sum=sum-a[l]+a[r-n+1];
l++,r++;
update(1,2*n,r,r,root,sum);
}
cout<<ans;
return 0;
}
TLE?线段树被卡常了,因此需要优化。其实直接可以优化标记下溢的常数,如果不存在标记,则无需下溢,这样就可以通过了。
代码如下:
#include<bits/stdc++.h>
using namespace std;
#define rep(i,a,b) for(int i=a;i<=b;i++)
#define deap(i,a,b) for(int i=a;i>=b;i--)
#define in(a) a=read()
const int N = 2e6+5;
const int inf = INT_MAX;
inline long long read() {
long long x=0,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-48;
ch=getchar();
}
return x*f;
}
void fast() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
}
int c[N];
struct linetree{
int cl,cr,ans,lazy;
};
vector<linetree>t;
int build(int l,int r){
linetree tmp;
if(l==r){
tmp.cl=tmp.cr=-1;
tmp.ans=c[l];
tmp.lazy=0;
t.push_back(tmp);
return t.size()-1;
}
int mid=(l+r)>>1;
tmp.cl=build(l,mid);
tmp.cr=build(mid+1,r);
tmp.ans=min(t[tmp.cl].ans,t[tmp.cr].ans);
tmp.lazy=0;
t.push_back(tmp);
return t.size()-1;
}
void push_down(int idx){
if(!t[idx].lazy)return;
t[t[idx].cl].ans+=t[idx].lazy;
t[t[idx].cr].ans+=t[idx].lazy;
t[t[idx].cl].lazy+=t[idx].lazy;
t[t[idx].cr].lazy+=t[idx].lazy;
t[idx].lazy=0;
}
void update(int l,int r,int ql,int qr,int idx,int val){
if(qr<l||r<ql)return;
if(ql<=l&&r<=qr){
t[idx].ans+=val;
t[idx].lazy+=val;
return;
}
int mid=(l+r)>>1;
push_down(idx);
update(l,mid,ql,qr,t[idx].cl,val);
update(mid+1,r,ql,qr,t[idx].cr,val);
t[idx].ans=min(t[t[idx].cl].ans,t[t[idx].cr].ans);
}
int query(int l,int r,int ql,int qr,int idx){
if(qr<l||r<ql)return inf;
if(ql<=l&&r<=qr){
return t[idx].ans;
}
int mid=(l+r)>>1;
push_down(idx);
return min(query(l,mid,ql,qr,t[idx].cl),query(mid+1,r,ql,qr,t[idx].cr));
t[idx].ans=min(t[t[idx].cl].ans,t[t[idx].cr].ans);
}
int a[N];
signed main() {
//fast();
int n,p=1,ans=0,sum;
in(n);
rep(i,1,n)in(a[i]);
rep(i,1,n){
c[i]=c[i-1]+a[i];
}
sum=c[n];
int root=build(1,2*n);
int l=1,r=n;
while(l<=n){
if(query(1,2*n,l,r,root)>=0)ans++;
update(1,2*n,l,r,root,-a[l]);
sum=sum-a[l]+a[r-n+1];
l++,r++;
update(1,2*n,r,r,root,sum);
}
cout<<ans;
return 0;
}
\(K\) 题:P1725 琪露诺
考虑动态规划,可得转换方程:\(dp_i=\max_{i-R\le j\le i-L}dp_j+A_i\)。哦,单点修改,区间极值,线段树!
好了,这题写完了。
代码如下:
#include<bits/stdc++.h>
using namespace std;
//#define int long long
#define I_love_Foccarus return
#define rep(i,a,b) for(int i=a;i<=b;i++)
#define deap(i,a,b) for(int i=a;i>=b;i--)
#define in(a) a=read()
#define fi first
#define se second
const int N = 3e5 + 5;
const int inf = INT_MAX;
inline int read() {
int x=0,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-48;
ch=getchar();
}
I_love_Foccarus x*f;
}
void fast() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
}
int a[N],dp[N];
struct linetree{
int cl,cr,ans,lazy;
};
vector<linetree>t;
int build(int l,int r){
linetree tmp;
if(l==r){
tmp.cl=tmp.cr=-1;
tmp.ans=0;
tmp.lazy=0;
t.push_back(tmp);
return t.size()-1;
}
int mid=(l+r)>>1;
tmp.cl=build(l,mid);
tmp.cr=build(mid+1,r);
tmp.ans=max(t[tmp.cl].ans,t[tmp.cr].ans);
tmp.lazy=0;
t.push_back(tmp);
return t.size()-1;
}
void push_down(int idx){
if(!t[idx].lazy)return;
t[t[idx].cl].ans+=t[idx].lazy;
t[t[idx].cr].ans+=t[idx].lazy;
t[t[idx].cl].lazy+=t[idx].lazy;
t[t[idx].cr].lazy+=t[idx].lazy;
t[idx].lazy=0;
}
void update(int l,int r,int ql,int qr,int idx,int val){
if(qr<l||r<ql)return;
if(ql<=l&&r<=qr){
t[idx].ans=val;
t[idx].lazy=val;
return;
}
int mid=(l+r)>>1;
push_down(idx);
update(l,mid,ql,qr,t[idx].cl,val);
update(mid+1,r,ql,qr,t[idx].cr,val);
t[idx].ans=max(t[t[idx].cl].ans,t[t[idx].cr].ans);
}
int query(int l,int r,int ql,int qr,int idx){
if(qr<l||r<ql)return INT_MIN;
if(ql<=l&&r<=qr){
return t[idx].ans;
}
int mid=(l+r)>>1;
push_down(idx);
return max(query(l,mid,ql,qr,t[idx].cl),query(mid+1,r,ql,qr,t[idx].cr));
t[idx].ans=max(t[t[idx].cl].ans,t[t[idx].cr].ans);
}
signed main() {
//fast();
int n,l,r;
in(n),in(l),in(r);
rep(i,0,n)in(a[i]);
n+=l;
int root=build(0,n);
rep(i,l,n){
int sum=query(0,n,max(i-r,0),i-l,root);
update(0,n,i,i,root,sum+a[i]);
}
cout<<query(0,n,n,n,root);
I_love_Foccarus 0;
}
\(L\) 题:P3522 [POI2011] TEM-Temperature
切绿题好爽哈。
思路:用单调队列进行维护,队首维护正确性,队尾维护单调性,正确性需要满足队首表示的那天的最低温度需要满足小于当前枚举的最高温度,也就是维护题目要求的段内可能温度不降。单调性就是维护单日最低温的不上升序列,这样就可以确定温度的左边界,每次枚举的答案就是最后一个不满足正确性的单调最大左边界与当前 \(i\) 的差。然后对每次枚举的答案取 max 就行了。
代码如下:
#include<bits/stdc++.h>
using namespace std;
//#define int long long
#define rep(i,a,b) for(int i=a;i<=b;i++)
#define deap(i,a,b) for(int i=a;i>=b;i--)
#define in(a) a=read()
const int N = 1e6+5;
const int inf = INT_MAX;
inline int read() {
int x=0,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-48;
ch=getchar();
}
return x*f;
}
void fast() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
}
int l[N],r[N],n,ans,nl;
deque<int>q;
signed main() {
//fast();
in(n);
rep(i,1,n)in(l[i]),in(r[i]);
rep(i,1,n){
while(!q.empty()&&l[q.front()]>r[i])nl=q.front(),q.pop_front();
if(!q.empty())
ans=max(ans,i-nl);
while(!q.empty()&&l[q.back()]<l[i])q.pop_back();
q.push_back(i);
}
cout<<ans;
return 0;
}
\(M\) 题:P2698 [USACO12MAR] Flowerpot S
这题很明显可以二分答案,以区间长度为二分对象。那么判定的函数怎么写呢?可以模拟一个滑动窗口,通过单调队列实现,但是我不会,所以我用 ST 表 \(A\) 掉的这道题。
用 ST 表预处理好后,直接在值域范围内扫描,通过 ST 实现最大/最小值的查询,然后相减判定答案是否成立。
由于数据过水,本该 MTE 的代码通过了本题,但我懒得改了。
代码如下:
#include<bits/stdc++.h>
using namespace std;
//#define int long long
#define I_love_Foccarus return
#define rep(i,a,b) for(int i=a;i<=b;i++)
#define deap(i,a,b) for(int i=a;i>=b;i--)
#define in(a) a=read()
#define fi first
#define se second
const int N = 5e5 + 5;
const int inf = INT_MAX;
inline int read() {
int x=0,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-48;
ch=getchar();
}
I_love_Foccarus x*f;
}
void fast() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
}
int n,d;
pair<int,int>a[N];
int st[N],dp[N][20][2],m;
signed main() {
//fast();
in(n),in(d);
rep(i,1,N)dp[i][0][1]=inf;
rep(i,1,n) {
int x,y;
in(x),in(y);
a[i]=make_pair(x,y);
dp[x][0][0]=max(dp[x][0][0],y);
dp[x][0][1]=min(dp[x][0][1],y);
m=max(m,x);
}
st[1]=0;
rep(i,2,m) st[i]=st[i>>1]+1;
rep(j,1,st[m])
rep(i,1,m-(1<<j)+1) {
dp[i][j][0]=max(dp[i][j-1][0],dp[i+(1<<(j-1))][j-1][0]);
dp[i][j][1]=min(dp[i][j-1][1],dp[i+(1<<(j-1))][j-1][1]);
}
int ls=1,rs=m,ans=-1;
while(ls<=rs) {
int mid=(ls+rs)>>1,p=0;
rep(i,1,m-mid) {
int l=i,r=i+mid;
int maxx=max(dp[l][st[r-l+1]][0],dp[r-(1<<st[r-l+1])+1][st[r-l+1]][0]);
int minx=min(dp[l][st[r-l+1]][1],dp[r-(1<<st[r-l+1])+1][st[r-l+1]][1]);
if(maxx-minx>=d) {
p=1;
break;
}
}
if(p)ans=mid,rs=mid-1;
else ls=mid+1;
}
cout<<ans;
I_love_Foccarus 0;
}
\(N\) 题:P4824 [USACO15FEB] Censoring S
对于某个想参考我代码的大哥哥:

我的思路并不怎么明确,就是在线 KMP 进行字符串匹配,用一个栈来维护,建立该栈的 nex 数组,然后在匹配过程中删除超出的字符,最终得出我们想要的答案(伦敦大雾)。虽然可以直接上 AC 自动机,或许这样我就可以讲得更明白些,但我太懒,没有这么做。
这是代码,自行脑补。
代码如下:
#include<bits/stdc++.h>
using namespace std;
//#define int long long
#define I_love_Foccarus return
#define in(a) a=read()
const int N = 1e7 + 5;
const int inf = INT_MAX;
inline int read() {
int x=0,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-48;
ch=getchar();
}
I_love_Foccarus x*f;
}
void fast() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
}
int cnt;
char s[N],t[N<<1];
char stk[N<<1];
int nex[N<<1];
signed main() {
fast();
cin>>t>>s;
int m=strlen(t),n=strlen(s);
for(int i=0;i<m;i++) s[i+n]=t[i];
nex[0]=0,stk[0]=s[0];
for(int i=1;i<n+m;i++){
stk[++cnt]=s[i];
int j=nex[cnt-1];
while(j>0&&stk[cnt]!=stk[j]) j=nex[j-1];
if(stk[cnt]==stk[j])j++;
nex[cnt]=j;
if(nex[cnt]==n){
int k=n;
while(k--) cnt--;
}
}
stk[cnt+1]=0;
cout<<(stk+n);
I_love_Foccarus 0;
}
中断
后面因为要复习生地,开始挂机……

浙公网安备 33010602011771号