十月马上过完了,十一月对我好一点
T1 P1725 琪露诺
用到了单调队列优化dp,我们要注意当 R ≥ 2 ∗ L R\ge2*L R≥2∗L时 f [ R ] f[R] f[R]不一定为 a [ R ] a[R] a[R],首先我们要从L开始,用 p [ ] p[] p[]数组来维护当前所能达到 x x x的最末尾的,然后我们使用滑动窗口(尺取法)来搜索答案。
不懂滑动窗口的看这里
c o d e code code
cpp
#include<bits/stdc++.h>
using namespace std;
const int N = 2e5 + 10;
int n, l, r;
int a[N];//信仰点
int q[N];
int f[N];
int ans;//记录信仰点的最大值
void init(){
memset(f,0xcf,sizeof(f));
}
int main() {
scanf("%d%d%d", &n, &l, &r);
for (int i = 0; i <= n; i++) {
scanf("%d", &a[i]);
}
int hed = 1;//头
int tal = 1;//尾
//将记录答案的f数组初始化
init();
ans = f[0];
f[0] = 0;//编号为0,信仰点为0
int p = 0;
for (int i = l; i <= n; i++) {
//让尾-1时条件,尾的信仰值小于头
while (hed <= tal && f[q[tal]] <= f[p]) {
tal--;
}
q[++tal] = p;
while (q[hed] + r < i) {
hed++;
}
f[i] = f[q[hed]] + a[i];
p++;
}
for (int i = n+1 - r; i <= n; i++) {
ans = max(ans, f[i]);
}
printf("%d", ans);
return 0;
}
T2 P8346 「Wdoi-6」最澄澈的空与海
这道题数据很优秀,开始看的第一眼以为是搜索,细一想原来是二分图,今天打开标签一看,是搜索+二分图。要做这道题首先我们得先知道一个结论:如果一个点只有一条边与之相连, 那么它只能和这条边上另一点相连。
然后我们就可以开始做啦,于是我们可以循环找这样的点,然后让他和这条边上另一个点配对。
对于另外一种情况,我们要找的这个点有很多条边,但是其他的都已经配对过,只剩下一个点没有配对,那么也可以直接相连,最后检查一遍所有的点是否配对即可。
实现也挺简单的,用到了do-while。
时间复杂度 O ( n 2 ) O(n^2) O(n2)
c o d e code code
cpp
#include <bits/stdc++.h>
using namespace std;
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 - '0', ch = getchar();
return x * f;
}
const int N = 1e7 + 10;
int t;
int n, m, u, v, res, tmp;//res:目前配对个数 tmp:上一次配对个数
bool vis[N];//vis[i]:i是否配对
int main() {
t = read();
while (t--) {
n = read();
m = read();
res = 0;
vector<int> mp[N];
for (int i = 1; i <= (n << 1); ++i) {
vis[i] = 0;
}
for (int i = 1; i <= m; ++i) {
u = read();
v = read() + n;
mp[u].push_back(v);
mp[v].push_back(u);
}
do {
tmp = res;
for (int i = 1; i <= (n << 1); ++i) {
if (vis[i]) continue;
int pos = -1;
int len=mp[i].size();
for (int j = 0; j < len; ++j) {
if (vis[mp[i][j]]) continue;
if (pos == -1) pos = j;
else {
pos = -1;
break;
}
}
if (pos != -1) { //配对点唯一
vis[i] = vis[mp[i][pos]] = 1;//将这两个配对
res += 2;
}
}
} while (tmp != res); //没有新的配对了
if (res == (n << 1)) puts("Yes");
else puts("No");
}
return 0;
}
T3 P4514 上帝造题的七分钟
God is a gril
这道题...寄掉了,树状数组,还是二维的,折磨
let us see see 这道题
二维树状数组是一个十分胡扯玄妙的算法。它和一维树状数组有着相同的地方,那就是 l o w b i t lowbit lowbit运算。在一维树状数组中,我们用 t r e e [ x ] tree[x] tree[x]记录右端点为 x x x,长度为 l o w b i t ( x ) lowbit(x) lowbit(x)的区间的区间和。我们同样可以类似地定义 t r e e [ x ] [ y ] tree[x][y] tree[x][y]为右下端点为 ( x , y ) (x,y) (x,y),高为 l o w b i t ( x ) lowbit(x) lowbit(x),宽为 l o w b i t ( y ) lowbit(y) lowbit(y)的区间的区间和。
前缀和跟差分还是挺会的,我们设差分数组 d i , j d_{i,j} di,j则有 a x , y = ∑ i = 1 x ∑ j = 1 y d i , j a_{x,y}=\sum_{i=1}^{x} \sum_{j=1}^y d_{i,j} ax,y=∑i=1x∑j=1ydi,j我们再来求一遍前缀和 f i , j = ∑ i = 1 x ∑ j = 1 y ∑ k = 1 i ∑ l = 1 y d i , j f_{i,j}=\sum_{i=1}^{x} \sum_{j=1}^{y} \sum_{k=1}^{i} \sum_{l=1}^{y} d_{i,j} fi,j=∑i=1x∑j=1y∑k=1i∑l=1ydi,j = ∑ i = 1 x ∑ j = 1 y ( x + 1 − i ) × ( y + 1 − j ) d i , j =\sum_{i=1}^{x} \sum_{j=1}^{y} (x+1-i) \times (y+1-j) d_{i,j} =∑i=1x∑j=1y(x+1−i)×(y+1−j)di,j
我们用二维数组来维护前缀和就行了。
c o d e code code
暴力 81 p t s 81pts 81pts
cpp
#include<bits/stdc++.h>
using namespace std;
long long n,m;
long long cc1[2510][2510];
long long cc2[2510][2510];
long long cc3[2510][2510];
long long cc4[2510][2510];
long long lowbit(long long x)
{
return x&(-x);
}
void add(long long a,long long b,long long delta)
{
if(a<1||n<a||b<1||m<b)
{
return;
}
for(long long i=a;i<=n;i+=lowbit(i))
{
for(long long j=b;j<=m;j+=lowbit(j))
{
cc1[i][j]+=delta;
cc2[i][j]+=delta*(a-1);
cc3[i][j]+=delta*(b-1);
cc4[i][j]+=delta*(a-1)*(b-1);
}
}
return;
}
void gai(long long a,long long b,long long c,long long d,long long ad)
{
add(a,b,ad);
add(c+1,b,-ad);
add(a,d+1,-ad);
add(c+1,d+1,ad);
}
long long Sum(long long a,long long b)
{
long long ans1=0,ans2=0,ans3=0,ans4=0;
for(long long i=a;i>=1;i-=lowbit(i))
{
for(long long j=b;j>=1;j-=lowbit(j))
{
ans1+=cc1[i][j];
ans2+=cc2[i][j];
ans3+=cc3[i][j];
ans4+=cc4[i][j];
}
}
ans1*=a*b;
return ans1-ans2*b-ans3*a+ans4;
}
int main()
{
char t;
cin>>t;
cin>>n>>m;
char op;
while(cin>>op)
{
if(op=='L')
{
long long a,b,c,d,delta;
cin>>a>>b>>c>>d>>delta;
gai(a,b,c,d,delta);
}
else
{
long long a,b,c,d;
cin>>a>>b>>c>>d;
cout<<Sum(c,d)-Sum(a-1,d)-Sum(c,b-1)+Sum(a-1,b-1)<<endl;
}
}
return 0;
}
cpp
#include<bits/stdc++.h>
const int N=2500;
int s[4][N][N],n,m;
int query(int typ,int x,int y){
int ans=0;
for(int i=x;i;i-=i&-i){
for(int j=y;j;j-=j&-j){
ans+=s[typ][i][j];
}
}
return ans;
}
void change(int typ,int x,int y,int delta){
for(int i=x;i<=n;i+=i&-i){
for(int j=y;j<=m;j+=j&-j){
s[typ][i][j]+=delta;
}
}
}
void modify(int x,int y,int delta){
change(0,x,y,delta);
change(1,x,y,y*delta);
change(2,x,y,x*delta);
change(3,x,y,x*y*delta);
}
int ask(int x,int y){
int ans=0;
ans+=(x+1)*(y+1)*query(0,x,y);
ans-=(x+1)*query(1,x,y);
ans-=(y+1)*query(2,x,y);
ans+=query(3,x,y);
return ans;
}
int main(){
char op[4];
int a,b,c,d,de;
scanf("%s%d%d",op,&n,&m);
while(scanf("%s%d%d%d%d",op,&a,&b,&c,&d)!=EOF){
if(op[0]=='L'){
scanf("%d",&de);
modify(a,b,de);
modify(a,d+1,-de);
modify(c+1,b,-de);
modify(c+1,d+1,de);
}
else{
int ans=0;
ans+=ask(c,d);
ans-=ask(a-1,d);
ans-=ask(c,b-1);
ans+=ask(a-1,b-1);
printf("%d\n",ans);
}
}
return 0;
}
T4 P7838 「Wdoi-3」夜雀 treating
优质题面,原题面表示看不懂
后宫佳丽三千,唯宠线段树一人。
特殊性质是能拿 5 p t s 5pts 5pts的,直接输出n+1即可,可惜没看懂题。
看到这个题发现一脸不可做的样子,考虑转化一下题意。 首先可以发现第一次取的数是固定的,于是将它先取掉。后面的操作就变成了:先取任意一个位置,然后再取剩下的中间的数。 可以发现这个东西可以用两个栈拟合得很好,于是考虑将左右两边的数分别插入两个栈中,每次取其中一个栈内任意元素,然后将另一个栈的栈顶删除。
考虑枚举最终选择的区间,将这个区间内的点称作 "关键点" 并在栈中标记。可以发现,这些关键点一定要作为栈顶删除。 于是原问题被转化成一个判定问题:对于两个栈,每次操作为,将其中一个的栈顶与另一个中的任意元素一起删除。需要知道是否存在某种删除方式使所有关键点都作为栈顶被删除。
考虑栈顶的情况,发现一共有 3 3 3种 :两个都是关键点,两个都不是关键点或一个是关键点而另一个不是。 考虑到出现不合法情况当且仅当其中一个栈内还有关键点而另一个栈内全都是关键点。而在三种情况中只有两个都不是关键点的情况才会浪费非关键点,所以我们要尽可能将关键点往上放,让它尽快消掉。这等价于尽可能先删除靠近栈顶的非关键点。 于是可以得到一个贪心的判定方法:对于两个都是非关键点的情况,将它们两个一起删除。对于一个是关键点而另一个不是的情况,将他们两个一起删除。对于两个都是关键点的情况,将其中一个与另一个栈中最靠近栈顶的非关键点匹配,这个可以用指针维护。 单次判定 O ( n ) O(n) O(n)
可以发现,如果将关键点看作 −1,非关键点看作 +1,那么这种标记关键点的方式合法当且仅当任何一个后缀和不小于 0. 这个可以用树状数组维护,每次区间端点移动则对后缀和区间修改,维护一下总体的最小值,判一下它是否为负数即可。
c o d e code code
cpp
#include<bits/stdc++.h>
using namespace std;
int n,m,a[500005],p[500005],ans;
struct segtree{
#define lc(x) (x<<1)
#define rc(x) (x<<1|1)
#define M(l,r) ((l+r)>>1)
int tree[2000005],tag[2000005];
void pushup(int p)
{
tree[p]=min(tree[lc(p)],tree[rc(p)]);
}
void maketag(int p,int k)
{
tree[p]+=k;
tag[p]+=k;
}
void pushdown(int p)
{
maketag(lc(p),tag[p]);
maketag(rc(p),tag[p]);
tag[p]=0;
}
void build(int p,int l,int r)
{
if(l==r)
{
tree[p]=-(2*l-1);
return ;
}
int mid=M(l,r);
build(lc(p),l,mid);
build(rc(p),mid+1,r);
pushup(p);
}
void modify(int p,int l,int r,int L,int R,int k)
{
if(L<=l&&r<=R)
{
maketag(p,k);
return ;
}
pushdown(p);
int mid=M(l,r);
if(L<=mid) modify(lc(p),l,mid,L,R,k);
if(mid<R) modify(rc(p),mid+1,r,L,R,k);
pushup(p);
}
}T;
void add(int x)
{
int l=abs(n+1-x)+1;
T.modify(1,1,n+1,l,n+1,2);
}
void del(int x)
{
int l=abs(n+1-x)+1;
T.modify(1,1,n+1,l,n+1,-2);
}
bool check(int l,int r)
{
int mx=T.tree[1];
int cnt=n+1-(r-l+1);
// cerr<<"?"<<l<<" "<<r<<" "<<cnt*2<<" "<<mx<<"\n";
if(cnt*2+mx>=0) return 1;
return 0;
}
int main()
{
cin>>n;
m=n*2+1;
for(int i=1;i<=m;i++)
{
cin>>a[i];
p[a[i]]=i;
}
int l=1;
T.build(1,1,n+1);
for(int i=1;i<=m;i++)
{
add(p[i]);
while(l<i&&(!check(l,i)||i-l+1>n+1)) del(p[l++]);
ans=max(ans,i-l+1);
}
cout<<ans;
return 0;
}
总结
阿江爆范围了,可悲,数据范围感觉比赛里面一般都long long直接用int的很少,基本上直接开long long就行了。
T4的特殊性质是一个悲点,题都没读懂哇,我勒个大豆,虽说 5 p t s 5pts 5pts,但也不少了。特殊性质的分应该拿上,基本上特殊性质的分也不难写。T3暴击寄掉了,暴力出奇迹,还是得多练练暴力。