【考后总结】6 月西安 NOI 模拟赛 2
6.15 冲刺国赛模拟 19
T1 矩阵
直接二维树状数组就能过。
另一种做法根据修改不超过 \(10^4\) 次,对操作分块,每 \(B\) 次分块暴力处理一次,剩下部分每次询问都暴力算。
点击查看代码
int n,m,q;
struct BinaryIndexedTree{
#define lowbit(x) (x&-x)
ll a[1005][1005];
inline void update(int x,int y,ll k){
for(int i=x;i<=n;i+=lowbit(i)){
for(int j=y;j<=m;j+=lowbit(j)){
a[i][j]+=k;
}
}
}
inline ll query(int x,int y){
ll res=0;
for(int i=x;i;i-=lowbit(i)){
for(int j=y;j;j-=lowbit(j)){
res+=a[i][j];
}
}
return res;
}
}B;
int main(){
freopen("matrix.in","r",stdin);
freopen("matrix.out","w",stdout);
n=read(),m=read(),q=read();
while(q--){
int opt=read();
if(opt==1){
int x=read(),y=read();
ll k;
scanf("%lld",&k);
B.update(x,y,k);
}
else{
int x=read(),y=read(),a=read(),b=read();
if(x>a) swap(x,a);
if(y>b) swap(y,b);
ll ans=B.query(a,b)-B.query(a,y-1)-B.query(x-1,b)+B.query(x-1,y-1);
printf("%lld\n",ans);
}
}
return 0;
}
T2 种草
考虑 \(a\) 全相等的特殊性质,观察到这可以看做分成 \(a\) 层,每层选出的操作都不相交,先建出 \(S\to 1\to 2\to\cdots\to n\to n+1\to T\),除 \(S\to 1\) 有容量 \(a\) 的限制外其他容量设成最大,且费用均为 \(0\)。
之后对于 \((l,r,w)\),连 \(l\) 向 \(r+1\) 的边,且容量量为 \(1\),费用是 \(w\),这样从 \(S\) 出发的 \(a\) 条流的每一条都只能从左向右,在最大费用流的限制下,就会尽量走 \(w\) 更大的部分。
考虑 \(a\) 不同怎么办,由于 \(a\) 单调不降,可以把刚才的 \(a\) 层改成每一层都有一个区间限制 \([x,n]\),且 \(x\) 单调不降,这样对于每个 \(k\le a_n\),找到第一个不小于 \(k\) 的 \(a_i\),由 \(S\) 向 \(i\) 连一条容量为 \(1\) 的边,这样就相当于限制了这一层只能从 \(i\) 开始,然后就要跑费用流了。
SPFA 的 EK 费用流不太能过,要用 Primal-Dual 原始对偶算法,复杂度是 \(O(m\log ma)\)。
点击查看代码
int n,m;
int a[maxn];
struct Segment{
int l,r,w;
Segment()=default;
Segment(int l_,int r_,int w_):l(l_),r(r_),w(w_){}
}Seg[maxn];
int S,T;
struct edge{
int to,nxt,lim,c;
}e[maxn*4+40];
int head[maxn*2+40],cnt;
int deg[maxn];
inline void add_edge(int u,int v,int w,int c){
e[++cnt].to=v,e[cnt].nxt=head[u],e[cnt].lim=w,e[cnt].c=c,head[u]=cnt;
e[++cnt].to=u,e[cnt].nxt=head[v],e[cnt].lim=0,e[cnt].c=-c,head[v]=cnt;
++deg[v];
}
ll h[maxn];
inline void BFS(){
queue<int> q;
memset(h,0x3f,sizeof(h));
q.push(S);
h[S]=0;
while(!q.empty()){
int u=q.front();
q.pop();
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].to,w=e[i].c;
if(e[i].lim){
h[v]=min(h[v],h[u]+w);
--deg[v];
if(!deg[v]) q.push(v);
}
}
}
}
struct node{
int u;
ll d;
node()=default;
node(int u_,ll d_):u(u_),d(d_){}
bool operator<(const node& rhs)const{
return d>rhs.d;
}
};
ll dis[maxn];
bool vis[maxn];
int pre[maxn];
inline void Dijkstra(){
priority_queue<node> q;
memset(dis,0x3f,sizeof(dis));
memset(vis,0,sizeof(vis));
q.push(node(S,0));
dis[S]=0;
while(!q.empty()){
int u=q.top().u;
q.pop();
if(vis[u]) continue;
vis[u]=1;
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].to;
ll w=e[i].c+h[u]-h[v];
if(e[i].lim&&dis[u]+w<dis[v]){
dis[v]=dis[u]+w,pre[v]=i;
q.push(node(v,dis[v]));
}
}
}
}
inline ll mincost_maxflow(){
BFS();
ll mincost=0;
while(1){
Dijkstra();
if(dis[T]==0x3f3f3f3f3f3f3f3f) return -mincost;
for(int u=1;u<=T;++u) h[u]+=dis[u];
int flow=inf;
for(int u=T;u!=S;u=e[pre[u]^1].to){
flow=min(flow,e[pre[u]].lim);
}
for(int u=T;u!=S;u=e[pre[u]^1].to){
mincost+=1ll*e[pre[u]].c*flow;
e[pre[u]].lim-=flow,e[pre[u]^1].lim+=flow;
}
}
}
int main(){
freopen("weed.in","r",stdin);
freopen("weed.out","w",stdout);
n=read(),m=read();
for(int i=1;i<=n;++i) a[i]=read();
for(int i=1;i<=m;++i){
int l=read(),r=read(),w=read();
Seg[i]=Segment(l,r,w);
}
S=n+2,T=n+3;
cnt=1;
for(int i=1;i<=n;++i) add_edge(i,i+1,inf,0);
add_edge(n+1,T,inf,0);
for(int i=1;i<=m;++i) add_edge(Seg[i].l,Seg[i].r+1,1,-Seg[i].w);
for(int i=1,j=1;i<=n;++i){
while(j<=a[i]){
add_edge(S,i,1,0);
++j;
}
}
printf("%lld\n",mincost_maxflow());
return 0;
}
6.16 ACM 趣味赛
C The Longest Wall
签到题,两个 LDS 拼起来即可。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e3+10;
inline int read(){
int x=0,w=1;char c=getchar();
while(c<'0'||c>'9'){if(c=='-')w=-1;c=getchar();}
while(c<='9'&&c>='0'){x=(x<<3)+(x<<1)+c-'0';c=getchar();}
return x*w;
}
int n;
int a[maxn];
int f[maxn],g[maxn];
int main(){
n=read();
for(int i=1;i<=n;++i) a[i]=read();
a[0]=0x3f3f3f3f,a[n+1]=0x3f3f3f3f;
for(int i=1;i<=n;++i){
for(int j=0;j<i;++j){
if(a[i]<a[j]) f[i]=max(f[i],f[j]+1);
}
}
for(int i=n;i>=1;--i){
for(int j=n+1;j>i;--j){
if(a[i]<a[j]) g[i]=max(g[i],g[j]+1);
}
}
int ans=0;
for(int i=1;i<=n;++i) ans=max(ans,f[i]+g[i]-1);
printf("%d\n",ans);
return 0;
}
D Singular Matrix
行列式为 \(0\) 的充要条件是存在向量线性相关,加上一个取模的条件同样成立。
考虑统计线性无关的方阵个数,第一行是可以填除零向量外的 \((v^n-1)\) 种,第二行不能填和第一行共线的 \(v\) 种,所以可以填 \((v^n-v^2)\) 种。根据向量基本定理,第三行不能填由前两行可以表示出的 \(v^2\) 种,以此类推。最后算出来答案就是:
点击查看代码
inline int q_pow(int A,int B,int P){
int res=1;
while(B){
if(B&1) res=1ll*res*A%P;
A=1ll*A*A%P;
B>>=1;
}
return res;
}
int n,v,mod;
int ans;
int main(){
n=read(),v=read(),mod=read();
ans=q_pow(q_pow(v,1ll*n*n%(mod-1),mod),mod-2,mod);
int pwn=q_pow(v,n,mod),pw=1;
for(int i=1;i<=n;++i){
ans=1ll*ans*(pwn-pw+mod)%mod;
pw=1ll*pw*v%mod;
}
ans=(1-ans+mod)%mod;
printf("%d\n",ans);
return 0;
}
K Distribution
就是要求 \(sum-\max \equiv 0\pmod m\) 且 \(sum-\max\neq 0\) 的区间个数,最值考虑线段树+单调栈或分治。线段树没法维护模意义下最小值和区间修改,所以只能分治。
分治很好做,只有一个最大值的限制,开桶计算答案就行了。
点击查看代码
int n,m;
int a[maxn];
int mx[maxn],sum[maxn];
int tot[maxw];
ll ans;
void solve(int l,int r){
if(l==r) return;
int mid=(l+r)>>1;
solve(l,mid),solve(mid+1,r);
mx[mid]=a[mid],sum[mid]=a[mid]%m,mx[mid+1]=a[mid+1],sum[mid+1]=a[mid+1]%m;
for(int i=mid-1;i>=l;--i) mx[i]=max(mx[i+1],a[i]),sum[i]=(sum[i+1]+a[i])%m;
for(int i=mid+2;i<=r;++i) mx[i]=max(mx[i-1],a[i]),sum[i]=(sum[i-1]+a[i])%m;
for(int i=mid,j=mid;i>=l;--i){
while(j<r&&mx[j+1]<=mx[i]){
++j;
++tot[sum[j]];
}
ans+=tot[(mx[i]-sum[i]+m)%m];
}
for(int i=mid+1;i<=r;++i) tot[sum[i]]=0;
for(int i=mid+1,j=mid+1;i<=r;++i){
while(j>l&&mx[j-1]<mx[i]){
--j;
++tot[sum[j]];
}
ans+=tot[(mx[i]-sum[i]+m)%m];
}
for(int i=mid;i>=l;--i) tot[sum[i]]=0;
}
int main(){
n=read(),m=read();
for(int i=1;i<=n;++i) a[i]=read();
solve(1,n);
printf("%lld\n",ans);
return 0;
}
H Mass Troops
原题:Luogu-P3574 POI 2014 FAR-FarmCraft
设 \(f_u\) 激活 \(u\) 子树全部节点所用最短时间,那么合并子树就是找一个顺序分别走各个子树。具体形式应当是:\(\max\left(\sum 2siz+1+f_v\right)\),即完成 \(v\) 子树的时间以及走完前面子树并走到 \(v\) 的时间之和,我们希望最小化最大值。
一个经典套路是贪心的顺序考虑交换,即假设 \(x,y\) 两子树在枚举顺序中相邻,考虑交换顺序会有什么影响。
发现前面枚举的子树对答案的贡献是一样的,因此差别只在 \(x,y\) 之间,具体是比较 \(\max(f_x,2siz_x+1+f_y)\) 与 \(\max(f_y,2siz_y+1+f_x)\)。由于 \(f_x<2siz_y+1+f_x,f_y<2siz_x+1+f_y\),所以实际的大小关系只是由 \(2siz_x+1+f_y\) 与 \(2siz_y+1+f_x\) 决定的,移项之后按照 \(2siz_x-f_x\) 升序排序即可。
点击查看代码
int n;
int a[maxn];
vector<int> E[maxn];
int fa[maxn],siz[maxn],dfn[maxn],dfncnt;
void dfs1(int u,int f){
fa[u]=f,siz[u]=1,dfn[u]=++dfncnt;
for(int v:E[u]){
if(v==f) continue;
dfs1(v,u);
siz[u]+=siz[v];
}
}
int dp[maxn];
void dfs2(int u){
for(int v:E[u]){
dfs2(v);
}
sort(E[u].begin(),E[u].end(),[&](int x,int y){
return 2*siz[x]-dp[x]<2*siz[y]-dp[y];
});
int sum=0;
for(int v:E[u]){
dp[u]=max(dp[u],sum+1+dp[v]);
sum+=2*siz[v];
}
if(u==1) dp[u]=max(dp[u],2*n-2+a[1]);
else dp[u]=max(dp[u],a[u]);
}
int main(){
n=read();
for(int i=1;i<=n;++i) a[i]=read();
for(int i=1;i<n;++i){
int u=read(),v=read();
E[u].push_back(v);
E[v].push_back(u);
}
dfs1(1,0);
for(int i=2;i<=n;++i){
sort(E[i].begin(),E[i].end(),[&](int x,int y){
return dfn[x]>dfn[y];
});
E[i].pop_back();
}
dfs2(1);
printf("%d\n",dp[1]);
return 0;
}