把 AFO 之前口胡的题放这里,一句话题解。
题目计数:19。
和 HH的项链一样,维护一下第二个出现的值,然后差分,树状数组即可,时间复杂度 (mathcal O(nlog n))。
有公式:
推式子:
整除分块,时间复杂度 (mathcal O(n+sqrt{n}))。
发现 (y_i) 是单调的,求完和还是单调的。
那么二分一个 (W),对于序列每次维护一个前缀和,对每个区间差分一下,就能 (mathcal O(1)) 了,时间复杂度 (mathcal O((n+m)log w_i))。
(4.) P6625 [省选联考 2020 B 卷] 卡牌游戏
省选的橙题也是挺有意思的.jpg
首先,题解中的做法我并没有想到。
但着不代表这题不可做,,,
我们考虑什么时候收手合并,当然是负数了!
如果负数后面有个很大的数呢?
如果费力把后面的数纳入现在的卡,那么就会少一次合并机会,得不偿失,所以只要是非负数就合并为一个卡,同时更新 (sum)。
记录一下合并完的卡片,作为第一张,第二张用一个动态的指针来记录一下,记得判断是否已经到了最后一张卡片,如果是的话并且总和比 (0) 大,就加上,还有就是要注意 long long
。
时间复杂度是 (mathcal O(n))。
(Code):
#include"iostream"
#include"cstdio"
#include"cmath"
#include"cstring"
using namespace std;
#define read(x) scanf("%d",&x)
#define MAXN 100005
#define ll long long
int n,a[MAXN];
ll sum=0,fir=0;
ll now=0;
int lst=2;
int main()
{
read(n);for(int i=1;i<=n;i++) read(a[i]);
fir=a[1];
while(lst<=n)
{
now=fir+(ll)a[lst];
if(lst==n&&now>=0) sum+=now;
lst++;
int k=lst;
for(int i=k;i<=n;i++)
{
if(now>=0){sum+=now,fir=now;break;}
now+=a[i],lst++;
if(i==n&&now>=0) sum+=now;
}
}
printf("%lld
",sum);
return 0;
}
真就 (mathcal O(m^2)) 啊?
(dp) 似乎很显然,并没有蓝题难度。
记 (dp_i) 为最后敲的鼹鼠为第 (i) 只鼹鼠时最多的得分,那么:
然后得到所有 (dp) 数组中的最大值即可,注意 (dp_1=1) ,假设第一个一定能用。
#include"iostream"
#include"cstdio"
#include"cmath"
#include"cstring"
using namespace std;
#define MAXN 10005
int n,m;
struct node
{
int t,x,y;
}a[MAXN];
int dp[MAXN]={0},ans=1;
inline int read()
{
int x=0;
char c=getchar();
while(c>'9'||c<'0'){c=getchar();}
while(c<='9'&&c>='0'){x=(x<<1)+(x<<3)+c-'0';c=getchar();}
return x;
}
inline int dis(int l,int r){return abs(a[l].x-a[r].x)+abs(a[l].y-a[r].y);}
int main()
{
n=read(),m=read();
for(register int i=1;i<=m;++i) a[i].t=read(),a[i].x=read(),a[i].y=read();
dp[1]=1;
for(register int i=2;i<=m;++i)
{
for(register int j=m-1;j>=1;--j)
{
if(a[i].t-a[j].t>=dis(i,j)) dp[i]=max(dp[i],dp[j]+1),ans=max(ans,dp[i]);
}
}
printf("%d
",ans);
return 0;
}
(C++ 14+O_2) 就真快啊
(6.) UVA11572 唯一的雪花 Unique Snowflakes
紫书上给出了 (mathcal O(nlog n)) 的做法,所以我给出了写法比较麻烦一些的 (mathcal O(n)) 做法。
考虑对每个点维护下一个出现此颜色的位置,并在每个位置记录有是否有前面的点把他标记了。
最后维护一个指针标记一下,如果这个点和他下一个都在一个暂时的最小区间中,就更新最小区间,并不断处理扫过区间的弹出。
这样就能 (mathcal O(n)) 了。
详解见:Link
珂以看视频解决。
看标签大法好。
求出最大生成树,所有的最优方案都包含在这里面。
然后维护两点之间路径的最小值,显然可以用树剖 (+) 线段树或 ST 表维护,但是不会 ST 表了,只能写线段树了/kk。
复杂度 (mathcal O(qlog ^2 n))
代码:
#include"iostream"
#include"cstdio"
#include"cmath"
#include"algorithm"
#include"cstring"
using namespace std;
#define read(x) scanf("%d",&x)
#define MAXN 50005
#define inf 2147483647
int n,m,q;
struct node
{
int x,y,z;
}ed[MAXN];
struct edge
{
int to,nxt,w;
}e[MAXN<<1];
int head[MAXN],cnt=0,c=0;
int f[MAXN],fat[MAXN];
int tot[MAXN],son[MAXN];
int t[MAXN],vis[MAXN];
int cntt=0,topf[MAXN],id[MAXN];
int dis[MAXN],dep[MAXN];
struct Tree
{
int maxn,l,r;
}a[MAXN<<2];
int x,y;
inline void update(int k){a[k].maxn=min(a[k<<1].maxn,a[k<<1|1].maxn);}
void build(int k,int l,int r)
{
a[k].l=l,a[k].r=r;
if(l==r){a[k].maxn=t[l];return;}
int mid=(l+r)>>1;
build(k<<1,l,mid),build(k<<1|1,mid+1,r);
update(k);
return;
}
int query(int k,int l,int r)
{
if(l>r) return inf;
if(a[k].l==l&&a[k].r==r) return a[k].maxn;
int mid=(a[k].l+a[k].r)>>1;
if(r<=mid) return query(k<<1,l,r);
else if(l>mid) return query(k<<1|1,l,r);
else return min(query(k<<1,l,mid),query(k<<1|1,mid+1,r));
}
void add(int u,int v,int w)
{
e[++cnt].to=v;
e[cnt].w=w;
e[cnt].nxt=head[u];
head[u]=cnt;
}
void init(int n){for(int i=1;i<=n;i++) f[i]=i;}
int getf(int u){return f[u]=(f[u]==u)?u:getf(f[u]);}
bool merge(int u,int v){int t1=getf(u),t2=getf(v);if(t1!=t2){f[t2]=t1;return true;}else return false;}
bool cmp(node n,node m){return n.z>m.z;}
int dfs1(int cur,int fa,int deep)
{
vis[cur]=1;
fat[cur]=fa;
tot[cur]=1;
dep[cur]=deep;
int maxson=0;
for(int i=head[cur];i;i=e[i].nxt)
{
int j=e[i].to;
if(j==fa) continue;
tot[cur]+=dfs1(j,cur,deep+1);
if(maxson<tot[j]) maxson=tot[j],son[cur]=j,dis[cur]=e[i].w;
}
return tot[cur];
}
void dfs2(int cur,int fa,int top,int lng)
{
vis[cur]=1;
topf[cur]=top;
if(fa!=cur) cntt++,id[cur]=cntt,t[cntt]=lng;
else cntt++,id[cur]=cntt,t[cntt]=inf;
if(!son[cur]) return;
dfs2(son[cur],cur,top,dis[cur]);
for(int i=head[cur];i;i=e[i].nxt)
{
int j=e[i].to;
if(!vis[j]) dfs2(j,cur,j,e[i].w);
}
return;
}
int work(int x,int y)
{
if(getf(x)!=getf(y)) return -1;
int ans=inf;
while(topf[x]!=topf[y])
{
if(dep[topf[x]]<=dep[topf[y]]) swap(x,y);
ans=min(ans,query(1,id[topf[x]],id[x]));
x=fat[topf[x]];
}
if(dep[x]>dep[y]) swap(x,y);
if(x==y) return ans;
else return ans=min(ans,query(1,id[son[x]],id[y]));
}
int main()
{
read(n),read(m);
for(int i=1;i<=m;i++) read(ed[i].x),read(ed[i].y),read(ed[i].z);
sort(ed+1,ed+1+m,cmp),init(n);
for(int i=1;i<=m;i++)
{
if(merge(ed[i].x,ed[i].y))
{
c++;
add(ed[i].x,ed[i].y,ed[i].z),add(ed[i].y,ed[i].x,ed[i].z);
if(c==n-1) break;
}
}
for(int i=1;i<=n;i++) if(!vis[i]) fat[i]=i,dfs1(i,0,1);
memset(vis,0,sizeof(vis));
for(int i=1;i<=n;i++) if(!vis[i]) dfs2(i,i,i,0);
build(1,1,cntt);
read(q);
while(q--)
{
read(x),read(y);
printf("%d
",work(x,y));
}
return 0;
}
直接求一下深度,然后枚举每个深度的每一个点暴力更新即可。
怎么我写的这么长啊,怎么数据范围这么迷啊,怎么题解都用的拓扑排序啊。
时间复杂度 (mathcal O(nlog n+p))。
#include"iostream"
#include"cstdio"
#include"cstring"
#include"cmath"
#include"algorithm"
using namespace std;
#define MAXN 105
#define read(x) scanf("%d",&x)
int n,m;
int c[MAXN],u[MAXN];
struct edge
{
int to,nxt,w;
}e[MAXN*MAXN];
int head[MAXN],cnt=0;
int x,y,z;
struct node
{
int id,dep;
}a[MAXN];
struct ans
{
int id,val;
}b[MAXN];
int cntt=0;
int vis[MAXN],cc=0;
int beg[MAXN],mch[MAXN];
int maxd=0;
int flg=0;
bool cmp1(node n,node m){return n.dep<m.dep;}
bool cmp2(ans n,ans m){return n.id<m.id;}
void add(int u,int v,int c)
{
e[++cnt].to=v;
e[cnt].nxt=head[u];
e[cnt].w=c;
head[u]=cnt;
}
void dfs(int cur,int de)
{
a[++cc].id=cur,a[cc].dep=de,maxd=max(maxd,de);
vis[cur]=1;
for(int i=head[cur];i;i=e[i].nxt)
{
int j=e[i].to;
if(!vis[j]) dfs(j,de+1);
}
return;
}
int main()
{
read(n),read(m);
for(int i=1;i<=n;i++) read(c[i]),read(u[i]);
for(int i=1;i<=m;i++) read(x),read(y),read(z),add(x,y,z);
for(int i=1;i<=n;i++) if(c[i]) dfs(i,1);
sort(a+1,a+cc+1,cmp1);
for(int i=1;i<=cc;i++){if(!beg[a[i].dep]) beg[a[i].dep]=i;mch[a[i].dep]++;}
for(int i=1;i<=mch[1];i++)
{
int k=a[i].id;
if(c[k]<=0) continue;
for(int s=head[k];s;s=e[s].nxt)
{
int j=e[s].to;
c[j]+=e[s].w*c[k];
}
}
for(int i=2;i<=maxd;i++)
{
for(int j=0;j<mch[i];j++)
{
int k=a[beg[i]+j].id;
c[k]-=u[k];
if(c[k]<=0) continue;
for(int s=head[k];s;s=e[s].nxt)
{
c[e[s].to]+=e[s].w*c[k];
}
}
}
for(int i=beg[maxd];i<=cc;i++)
{
if(c[i]>0) b[++cntt].id=a[i].id,b[cntt].val=c[a[i].id];
}
if(!cntt){printf("NULL
");return 0;}
sort(b+1,b+cntt+1,cmp2);
for(int i=1;i<=cntt;i++) printf("%d %d
",b[i].id,b[i].val);
return 0;
}
我这么困,竟然还能 1A???
(10.) P6477 [NOI Online #2 提高组]子序列问题(民间数据)
颜色不同性问题,考虑枚举每一个点的贡献,这也是多次枚举中减少枚举唯独的常用技巧。
可是考试的时候不会啊......
套路的维护一个前面最后一个出现的 (pos) 数组。
然后考虑把 (f(i,i)) 到 (f(i,n)) 的贡献都加上。如果有新的贡献的话,再加上。
比如从前有 (3) 个颜色,现在加一个,那么最后加上的贡献为 (16-9=7=2*3+1)。
于是我们维护序列代表从 (i) 发起的序列现在有几种颜色了。
我们需要增加贡献的是出现新颜色的区间,因为其他区间后面已默认没有出现新颜色加上贡献了,然后把出现新颜色的区间颜色数都加一。
不懂?举个栗子。
序列: (1,2,3,1)
我们只考虑从 (1) 号数发起的区间的贡献。
在第一个数时,有 (0) 个颜色,贡献 (+1*4)(有四个由这里发起的子序列)。
在第二个数时,有 (1) 个颜色,贡献 (+3*3)。
在第三个数时,有 (2) 颜色,贡献 (+5*2)。
在第四个数时,因为 前面更新时在最后一个数的贡献已经是 (9) 所以不用加了。
我们用线段树来维护所有的起点的子序列,复杂度是 (mathcal O(nlog n))。
跑得慢啊
#include"iostream"
#include"cstdio"
#include"algorithm"
#include"cstring"
using namespace std;
#define MAXN 1000005
#define MOD 1000000007
#define ll long long
int n,num[MAXN];
struct node
{
int id,val;
}b[MAXN];
int lst=0,cnt=0;
int col[MAXN],pos[MAXN];
ll ans=0;
inline int read()
{
int x=0;
char c=getchar();
while(c<'0'||c>'9'){c=getchar();}
while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+c-'0';c=getchar();}
return x;
}
inline bool cmp(node n,node m){return n.val<m.val;}
inline void hsh(int n)
{
sort(b+1,b+n+1,cmp);
for(register int i=1;i<=n;i++)
{
if(b[i].val!=lst) lst=b[i].val,++cnt;
num[b[i].id]=cnt;
}
return;
}
struct Tree
{
int l,r;
ll sum,lazy;
}a[MAXN<<2];
inline void update(int k){a[k].sum=a[k<<1].sum+a[k<<1|1].sum;}
inline void build(int k,int l,int r)
{
a[k].l=l,a[k].r=r;
if(l==r){a[k].sum=0;return;}
int mid=(l+r)>>1;
build(k<<1,l,mid),build(k<<1|1,mid+1,r);
update(k);return;
}
inline void lazydown(int k)
{
if(a[k].l==a[k].r){a[k].lazy=0;return;}
a[k<<1].sum+=(a[k<<1].r-a[k<<1].l+1)*a[k].lazy;
a[k<<1|1].sum+=(a[k<<1|1].r-a[k<<1|1].l+1)*a[k].lazy;
a[k<<1].lazy+=a[k].lazy;
a[k<<1|1].lazy+=a[k].lazy;
a[k].lazy=0;
return;
}
inline void turn(int k,int l,int r)
{
if(a[k].lazy) lazydown(k);
if(a[k].l==l&&a[k].r==r)
{
a[k].sum+=(ll)(a[k].r-a[k].l+1);
a[k].lazy++;
return;
}
int mid=(a[k].l+a[k].r)>>1;
if(r<=mid) turn(k<<1,l,r);
else if(l>mid) turn(k<<1|1,l,r);
else turn(k<<1,l,mid),turn(k<<1|1,mid+1,r);
update(k);
}
inline ll query(int k,int l,int r)
{
if(a[k].lazy) lazydown(k);
if(a[k].l==l&&r==a[k].r) return a[k].sum;
int mid=(a[k].l+a[k].r)>>1;
if(r<=mid) return query(k<<1,l,r);
else if(l>mid) return query(k<<1|1,l,r);
else return query(k<<1,l,mid)+query(k<<1|1,mid+1,r);
}
int main()
{
n=read();
for(register int i=1;i<=n;i++) b[i].val=read(),b[i].id=i;
hsh(n);
for(register int i=1;i<=n;i++) pos[i]=col[num[i]],col[num[i]]=i;
build(1,1,n);
for(register int i=1;i<=n;i++)
{
ll now=query(1,pos[i]+1,i);
now=now*2+i-pos[i];
ans=(ans%MOD+(n-i+1)%MOD*now%MOD)%MOD;
turn(1,pos[i]+1,i);
}
printf("%lld
",ans%MOD);
return 0;
}//开O2才能过吧
挺妙的,其实就是从左到右扫一遍,如果从上一个起点到这里正好是可以均分的,那么下一个是一个新起点,这里的最后一个与下一块不沟通,所以操作次数从 (n) 中减一。
时间复杂度是 (mathcal O(n))。
#include"iostream"
#include"cstdio"
using namespace std;
#define read(x) scanf("%d",&x)
#define MAXN 105
int n,a[MAXN];
int sum=0,av=0;
int ans;
int main()
{
read(n);
ans=n;
for(int i=1;i<=n;i++) read(a[i]),av+=a[i];
av/=n;
for(int i=1;i<=n;i++)
{
sum+=a[i];
if(sum==av*i) ans--;
}
printf("%d
",ans);
return 0;
}
暴力暴力暴力
把往下和往右作为主导,然后暴力判断每一个格的 (gcd),然后贪心的选就好了。
复杂度是 (mathcal O(n^2log n)),并没有蓝题难度。
用 (01) 串代表是否被选,然后判断每一个状态是否可行。
可以开三维数组代表状态,共选几个位置,行数。
这里用一种方式压掉了行数这一维(难道这就是滚动数组?算了算了,状压 dp 我都没正经学过还 A 了这题呢),然而大大增加了码量。
然后空间复杂度应该是 (mathcal O(2^nk)),时间复杂度应该是 (mathcal O(2^{2n}n(n+k)))。
#include"iostream"
#include"cstdio"
#include"cstring"
#include"cmath"
using namespace std;
#define read(x) scanf("%d",&x)
#define MAXN (1<<9)+5
#define ll long long
int n,k;
ll a[MAXN][85],b[MAXN][85];
bool flag[10],mark=false;
int cnt=0;
ll ans=0;
int main()
{
read(n),read(k);
memset(a,0,sizeof(a));
memset(b,0,sizeof(b));
for(int s=0;s<(1<<n);s++)
{
mark=false,cnt=0;
for(int i=0;i<n;i++){if((1<<i)&s) flag[i]=1;else flag[i]=0;}
{
if(flag[i]&&flag[i-1]) mark=true;
if(flag[i]) cnt++;
}
if(flag[0]) cnt++;
if(mark) a[s][cnt]=0;
else a[s][cnt]=1ll;
}
for(int qwq=2;qwq<=n;qwq++)
{
if(qwq%2==0)
{
memset(b,0,sizeof(b));
for(int s1=0;s1<(1<<n);s1++)
{
for(int s2=0;s2<(1<<n);s2++)
{
mark=false,cnt=0;
for(int i=0;i<n;i++)
{
if(s2&(1<<i))
{
if(i>=1&&(s1&(1<<(i-1)))) mark=true;
if(s1&(1<<i)) mark=true;
if(i<n&&(s1&(1<<(i+1)))) mark=true;
}
if(mark) break;
}
for(int i=1;i<n;i++) if(((1<<i)&s2)&&((1<<(i-1))&s2)) mark=true;
if(mark) continue;
for(int i=0;i<n;i++) if(s2&(1<<i)) cnt++;
for(int i=cnt;i<=k;i++) b[s2][i]+=a[s1][i-cnt];
}
}
}
else
{
memset(a,0,sizeof(a));
for(int s1=0;s1<(1<<n);s1++)
{
for(int s2=0;s2<(1<<n);s2++)
{
mark=false,cnt=0;
for(int i=0;i<n;i++)
{
if(s2&(1<<i))
{
if(i>=1&&(s1&(1<<(i-1)))) mark=true;
if(s1&(1<<i)) mark=true;
if(i<n&&(s1&(1<<(i+1)))) mark=true;
}
if(mark) break;
}
for(int i=1;i<n;i++) if(((1<<i)&s2)&&((1<<(i-1))&s2)) mark=true;
if(mark) continue;
for(int i=0;i<n;i++) if(s2&(1<<i)) cnt++;
for(int i=cnt;i<=k;i++) a[s2][i]+=b[s1][i-cnt];
}
}
}
}
if(n%2==0) for(int s=0;s<(1<<n);s++) ans+=b[s][k];
else for(int s=0;s<(1<<n);s++) ans+=a[s][k];
printf("%lld
",ans);
}
错排问题。
递推式 :
冷静分析之后发现答案是:
复杂度是 (mathcal O(n+Tlog p))。
#include"iostream"
#include"cstdio"
#include"cmath"
using namespace std;
#define read(x) scanf("%d",&x)
#define MAXN 1000005
#define MOD 1000000007
int t,n,m;
int d[MAXN],jc[MAXN];
void get()
{
d[0]=1,jc[0]=1;
d[1]=0,d[2]=1;
jc[1]=1,jc[2]=2;
for(int i=3;i<=1000000;i++)
{
d[i]=1ll*(i-1)*(d[i-1]+d[i-2])%MOD;
jc[i]=1ll*jc[i-1]*i%MOD;
}
return;
}
int quickpow(int n,int k)
{
int ans=1,base=n;
while(k)
{
if(k&1) ans=1ll*ans*base%MOD;
k>>=1;
base=1ll*base*base%MOD;
}
return ans%MOD;
}
int inv(int a){return quickpow(a,MOD-2);}
int com(int n,int m){return 1ll*jc[n]*inv(1ll*jc[m]*jc[n-m]%MOD)%MOD;}
int main()
{
get();
read(t);
while(t--)
{
read(n),read(m);
printf("%d
",(int)(1ll*com(n,m)*d[n-m]%MOD));
}
return 0;
}
这不是线段树最大子段和板子题吗?
只是加了个单点修改,反正不是区间修改,就能搞。
时间复杂度 (mathcal O(mlog n))。
#include"iostream"
#include"cstdio"
#include"cmath"
#include"cstring"
using namespace std;
#define read(x) scanf("%d",&x)
#define MAXN 500005
int n,m;
int t[MAXN],type,l,r;
struct Tree
{
int l,r,pre,suf;
int sum,maxn;
Tree(){l=r=pre=suf=sum=maxn=0;}
}a[MAXN<<2];
inline void update(int k)
{
a[k].sum=a[k<<1].sum+a[k<<1|1].sum;
a[k].pre=max(a[k<<1].pre,a[k<<1].sum+a[k<<1|1].pre);
a[k].suf=max(a[k<<1|1].suf,a[k<<1|1].sum+a[k<<1].suf);
a[k].maxn=max(a[k<<1].suf+a[k<<1|1].pre,max(a[k].pre,max(a[k<<1].maxn,max(a[k<<1|1].maxn,a[k].suf))));
}
void build(int k,int l,int r)
{
a[k].l=l,a[k].r=r;
if(l==r){a[k].sum=a[k].pre=a[k].suf=a[k].maxn=t[l];return;}
int mid=(l+r)>>1;
build(k<<1,l,mid),build(k<<1|1,mid+1,r);
update(k);
}
void turn(int k,int x,int y)
{
if(a[k].l==a[k].r){a[k].sum=a[k].pre=a[k].suf=a[k].maxn=y;return;}
int mid=(a[k].l+a[k].r)>>1;
if(x<=mid) turn(k<<1,x,y);
else turn(k<<1|1,x,y);
update(k);
}
Tree query(int k,int l,int r)
{
if(a[k].l==l&&a[k].r==r) return a[k];
int mid=(a[k].l+a[k].r)>>1;
if(r<=mid) return query(k<<1,l,r);
else if(l>mid) return query(k<<1|1,l,r);
else
{
Tree ans,g=query(k<<1,l,mid),h=query(k<<1|1,mid+1,r);
ans.pre=max(g.pre,g.sum+h.pre);
ans.suf=max(h.suf,h.sum+g.suf);
ans.sum=g.sum+h.sum;
ans.maxn=max(g.maxn,max(h.maxn,max(ans.pre,max(ans.suf,g.suf+h.pre))));
return ans;
}
}
int main()
{
read(n),read(m);
for(int i=1;i<=n;i++) read(t[i]);
build(1,1,n);
for(int i=1;i<=m;i++)
{
read(type),read(l),read(r);
if(type==1&&l>r) swap(l,r);
if(type==1) printf("%d
",query(1,l,r).maxn);
else turn(1,l,r);
}
return 0;
}
这个操作二这么少就不正常。
考虑每一次操作二什么时候被影响,其实是当被操作一中 (x) 整除的时候。
那就对每一个 (x) 打标记,然后操作二中看他有多少因数。
设有 (m_1) 个操作一, (m_2) 个操作二,那么复杂度是 (mathcal O(m_1+m_2sqrt{x}))。
#include"iostream"
#include"cstdio"
using namespace std;
#define read(x) scanf("%d",&x)
#define MAXN 1000005
int n,m;
int a[MAXN],c[MAXN];
int t,x,y;
inline int dfs(int n)
{
int ans=0;
for(int i=1;i*i<=n;i++)
{
if(n%i==0)
{
if(i*i==n) ans+=c[i];
else ans=ans+c[i]+c[n/i];
}
}
return ans;
}
int main()
{
read(n),read(m);
for(int i=1;i<=n;i++) read(a[i]);
for(int i=1;i<=m;i++)
{
read(t),read(x);
if(t==1) read(y),c[x]+=y;
else printf("%d
",a[x]+dfs(x));
}
return 0;
}
主席树板子。
能卡 (21) 倍空间,(21) 倍空间 (WA),更少 (RE)。
代码不好看,不贴了。
时间复杂度 (mathcal O((n+m)log n))。
又是一道状压 (dp)。
考虑用 (dp[s1][s2][k]) 代表枚举到第 (k) 行得到的上一行状态为 (s_1),上上行状态为 (s_2) 的最大方案数。
然后暴力判是否合法即可。
这样空间复杂度是 (mathcal O(2^{2n}m)),时间复杂度是 $$。
考虑像 互不侵犯 那题一样写一个 假的 滚动数组,空间复杂度就是 (mathcal O(2^{2n})) 了。
时间稍微卡一些能过(确信)。
看了看题解,发现题解的方法真妙啊。
我们可以对每一行先排除掉一些不合法的答案,然后存起来,直接枚举这些合法的就好了。
不会吧不会吧,就我一个人写的平衡树吗?<-太菜了,看不出树状数组
我们考虑每个询问前面的区间对此询问的贡献。
显然,只有两个区间有交集,才能对答案造成贡献。
然而交集不好维护,思考从反面解决问题,即哪些区间对答案没有贡献。
显然修改区间 ([l,r]) 对询问区间 ([L,R]) 没有贡献,当且仅当 (l>R) 或 (r<L)。由于 (lleq r),则两种情况不可能同时满足。
其实有点像偏序问题,但我们不向那方面想试试。
那么我们维护两颗平衡树,分别代表修改的左右区间的集合,然后每次询问询问区间的左右端点的排名即可。
这里是用替罪羊树实现的,复杂度是 (mathcal O(mlog m))。
#include"iostream"
#include"cstdio"
#include"cmath"
#include"algorithm"
using namespace std;
#define read(x) scanf("%d",&x)
#define MAXN 100005
#define alpha 0.7666
int n,m;
int t,l,r;
int sum=0;
struct Tree
{
int root,cnt,c;
int ls[MAXN],rs[MAXN];
int wn[MAXN],sh[MAXN];
int val[MAXN];
int rt[MAXN];
bool judge(int k){return wn[k]&&(double)sh[k]*alpha<(double)max(sh[ls[k]],sh[rs[k]]);}
void update(int k){sh[k]=sh[ls[k]]+sh[rs[k]]+wn[k];}
void unfold(int k)
{
if(!k) return;
unfold(ls[k]);
if(wn[k]) rt[++c]=k;
unfold(rs[k]);
}
int build(int l,int r)
{
if(l>=r) return 0;
int mid=(l+r)>>1;
ls[rt[mid]]=build(l,mid);
rs[rt[mid]]=build(mid+1,r);
update(rt[mid]);
return rt[mid];
}
void bal(int &k){c=0;unfold(k);k=build(1,c+1);}
void insert(int &k,int v)
{
if(!k)
{
k=++cnt;
if(!root) root=1;
wn[k]=sh[k]=1,val[k]=v;
ls[k]=rs[k]=0;
return;
}
else
{
if(val[k]==v) wn[k]++;
else if(val[k]>v) insert(ls[k],v);
else insert(rs[k],v);
update(k);
if(judge(k)) bal(k);
}
return;
}
int rkup(int k,int v)
{
if(!k) return 0;
if(val[k]==v) return sh[ls[k]];
else if(val[k]>v) return rkup(ls[k],v);
else return wn[k]+sh[ls[k]]+rkup(rs[k],v);
}
int rkdown(int k,int v)
{
if(!k) return 0;
if(val[k]==v) return sh[ls[k]]+wn[k];
else if(val[k]>v) return rkdown(ls[k],v);
else return wn[k]+sh[ls[k]]+rkdown(rs[k],v);
}
}Tl,Tr;
int main()
{
read(n),read(m);
for(int i=1;i<=m;i++)
{
read(t),read(l),read(r);
if(t==1)
{
Tl.insert(Tl.root,r);
Tr.insert(Tr.root,l);
sum++;
}
else
{
int ans=sum;
int now=Tl.rkup(Tl.root,l);
ans-=now;
now=Tr.rkdown(Tr.root,r);
now=sum-now;
ans-=now;
printf("%d
",ans);
}
}
return 0;
}