POI2011题解

时间:2023-03-10 03:43:49
POI2011题解

POI2011题解

2214先咕一会。。。

[BZOJ2212][POI2011]Tree Rotations

线段树合并模板题。

#include<cstdio>
#include<algorithm>
using namespace std;
int gi(){
int x=0,w=1;char ch=getchar();
while ((ch<'0'||ch>'9')&&ch!='-') ch=getchar();
if (ch=='-') w=0,ch=getchar();
while (ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
return w?x:-x;
}
const int N = 2e5+5;
struct seg{int ls,rs,sz;}t[N*80];
int n,tot;long long s1,s2,ans;
int build(int l,int r,int p){
int x=++tot,mid=l+r>>1;t[x].sz=1;
if (l==r) return x;
if (p<=mid) t[x].ls=build(l,mid,p);
else t[x].rs=build(mid+1,r,p);
return x;
}
int merge(int x,int y){
if (!x||!y) return x|y;
int z=++tot;t[z].sz=t[x].sz+t[y].sz;
s1+=1ll*t[t[x].ls].sz*t[t[y].rs].sz;
s2+=1ll*t[t[x].rs].sz*t[t[y].ls].sz;
t[z].ls=merge(t[x].ls,t[y].ls);
t[z].rs=merge(t[x].rs,t[y].rs);
return z;
}
int dfs(){
int x=gi();if (x) return build(1,n,x);
int ls=dfs(),rs=dfs();s1=s2=0;
x=merge(ls,rs);ans+=min(s1,s2);return x;
}
int main(){n=gi();dfs();printf("%lld\n",ans);return 0;}

[BZOJ2213][Poi2011]Difference

记\(s_{i,j}\)表示前\(i\)个位置里字符\(j\)的数量。我们相当于是要求\(\max\{(s_{r,a}-s_{l,a})-(s_{r,b}-s_{l,b})\}=\max\{(s_{r,a}-s_{r,b})-(s_{l,a}-s_{l,b})\}\),其中\(s_{r,b}-s_{l,b}\neq0\)。

所以只需要维护前缀\(s_{l,a}-s_{l,b}\)的最小值即可,每加一个字符至多只会使\(26\)个最小值发生改变,但因为\(s_{r,b}-s_{l,b}\neq0\)的这个烦人的限制,所以还需要记录一下更新最小值的位置。复杂度\(O(26n)\)。

最后还要\(O(26^2)\)判一遍。给组数据:\(s=\)'aab'

#include<cstdio>
#include<algorithm>
using namespace std;
int n,sum[26],las[26],p[26][26],mn[26][26],ans;char s[1000005];
int main(){
scanf("%d%s",&n,s+1);
for (int i=1;i<=n;++i){
int c=s[i]-'a';++sum[c];las[c]=i;
for (int j=0;j<26;++j)
if (sum[j])
ans=max(ans,sum[c]-sum[j]-mn[c][j]-(las[j]==p[c][j]));
for (int j=0;j<26;++j)
if (sum[j]-sum[c]<mn[j][c])
mn[j][c]=sum[j]-sum[c],p[j][c]=i;
}
for (int i=0;i<26;++i)
for (int j=0;j<26;++j)
if (sum[j])
ans=max(ans,sum[i]-sum[j]-mn[i][j]-(las[j]==p[i][j]));
printf("%d\n",ans);return 0;
}

[BZOJ2215][Poi2011]Conspiracy

先构造出一组合法方案。可以使用\(\mbox{2-sat}\),对于任两个点,如果他们之间有边则不能同时存在于独立集中,否则不能同时存在与团中。这样的复杂度是\(O(n^2)\)的。

然后考虑从当前方案出发生成其余的方案。可以发现,一个集合中不可能有超过一个点进入另一个集合,所以新方案生成只有三种方法:从独立集中丢一个点进团,从团中丢一个点进独立集,或者是双方交换一对点。

所以就维护一下每个点是不是只与对方集合中的某一个点冲突,然后统计答案即可。注意一下两个点集的数量。

#include<cstdio>
#include<algorithm>
using namespace std;
int gi(){
int x=0,w=1;char ch=getchar();
while ((ch<'0'||ch>'9')&&ch!='-') ch=getchar();
if (ch=='-') w=0,ch=getchar();
while (ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
return w?x:-x;
}
const int N = 5005;
int n,a[N][N],to[N*N],nxt[N*N],head[N<<1],cnt,dfn[N<<1],low[N<<1],tim,vis[N<<1],S[N<<1],bel[N<<1],scc,col[N],ban[N],tot[2],ans;
void link(int u,int v){
to[++cnt]=v;nxt[cnt]=head[u];head[u]=cnt;
u^=1;v^=1;swap(u,v);
to[++cnt]=v;nxt[cnt]=head[u];head[u]=cnt;
}
void Tarjan(int u){
dfn[u]=low[u]=++tim;vis[S[++S[0]]=u]=1;
for (int e=head[u];e;e=nxt[e])
if (!dfn[to[e]]) Tarjan(to[e]),low[u]=min(low[u],low[to[e]]);
else if (vis[to[e]]) low[u]=min(low[u],dfn[to[e]]);
if (dfn[u]==low[u]){
++scc;int x;
do x=S[S[0]--],vis[x]=0,bel[x]=scc;while (x^u);
}
}
int main(){
n=gi();
for (int i=1,j;i<=n;++i){
j=gi();while (j--) a[i][gi()]=1;
}
for (int i=1;i<=n;++i)
for (int j=i+1;j<=n;++j)
if (a[i][j]) link(i<<1|1,j<<1);
else link(i<<1,j<<1|1);
for (int i=2;i<=(n<<1|1);++i) if (!dfn[i]) Tarjan(i);
for (int i=1;i<=n;++i) if (bel[i<<1]==bel[i<<1|1]) return puts("0"),0;
for (int i=1;i<=n;++i) col[i]=bel[i<<1]<bel[i<<1|1],++tot[col[i]];
if (tot[0]&&tot[1]) ++ans;
for (int i=1;i<=n;++i)
for (int j=1;j<=n;++j)
if (col[i]!=col[j]&&a[i][j]!=col[j]){
if (!ban[i]) ban[i]=j;
else {ban[i]=-1;break;}
}
if (tot[0]>1)
for (int i=1;i<=n;++i)
if (col[i]==0&&!ban[i]) ++ans;
if (tot[1]>1)
for (int i=1;i<=n;++i)
if (col[i]==1&&!ban[i]) ++ans;
if (tot[0]&&tot[1])
for (int i=1;i<=n;++i)
for (int j=i+1;j<=n;++j)
if (col[i]!=col[j]&&(ban[i]==0||ban[i]==j)&&(ban[j]==0||ban[j]==i)) ++ans;
printf("%d\n",ans);return 0;
}

[BZOJ2216][Poi2011]Lightning Conductor

决策单调性模板题。

#include<cstdio>
#include<algorithm>
#include<cmath>
using namespace std;
int gi(){
int x=0,w=1;char ch=getchar();
while ((ch<'0'||ch>'9')&&ch!='-') ch=getchar();
if (ch=='-') w=0,ch=getchar();
while (ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
return w?x:-x;
}
const int N = 5e5+5;
int n,hd,tl;double a[N],dp1[N],dp2[N];
struct node{int p,l,r;}q[N];
double cal(int j,int i){
return a[j]+sqrt(i-j)-a[i];
}
int binary(int x,int y){
int l=q[tl].l,r=n,res=n;
while (l<=r){
int mid=l+r>>1;
if (cal(x,mid)>cal(y,mid)) res=mid,r=mid-1;
else l=mid+1;
}
return l;
}
void work(double *dp){
hd=1,tl=0;
for (int i=1;i<=n;++i){
++q[hd].l;
if (hd<=tl&&q[hd].l>q[hd].r) ++hd;
if (hd>tl||cal(i,n)>cal(q[tl].p,n)){
while (hd<=tl&&cal(i,q[tl].l)>cal(q[tl].p,q[tl].l)) --tl;
if (hd>tl) q[++tl]=(node){i,i,n};
else{
int x=binary(i,q[tl].p);
q[tl].r=x-1;q[++tl]=(node){i,x,n};
}
}
dp[i]=cal(q[hd].p,i);
}
}
int main(){
n=gi();
for (int i=1;i<=n;++i) a[i]=gi();
work(dp1);
reverse(a+1,a+n+1);
work(dp2);
for (int i=1;i<=n;++i) printf("%d\n",(int)ceil(max(dp1[i],dp2[n-i+1])));
return 0;
}

[BZOJ2217][Poi2011]Lollipopz

因为只有\(1\)或者\(2\),所以对这个序列求前缀和后,不能用前缀和表示的数一定不相邻。

或者说,如果一个数\(x\)不能被某个前缀和表示出来,那么\(x-1\)跟\(x+1\)都一定可以被表示出来。(边界情况除外)

如果询问的\(x\)可以被前缀和表示出来我们就直接用这个前缀,否则,找到\(x+1\)对应的前缀,把左右端点同时右移,直到左端点变成\(1\)或者右端点\(+1\)变成\(1\),就可以使这个区间和\(-1\)从而表示出\(x\)。

#include<cstdio>
#include<algorithm>
using namespace std;
int gi(){
int x=0,w=1;char ch=getchar();
while ((ch<'0'||ch>'9')&&ch!='-') ch=getchar();
if (ch=='-') w=0,ch=getchar();
while (ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
return w?x:-x;
}
const int N = 1e6+5;
int n,m,sum[N],f[N<<1],len[N];char s[N];
int main(){
n=gi();m=gi();scanf("%s",s+1);
for (int i=1;i<=n;++i) sum[i]=sum[i-1]+(s[i]=='W'?1:2),f[sum[i]]=i;
for (int i=n;i;--i) if (s[i]=='T') len[i]=len[i+1]+1;
while (m--){
int x=gi();
if (x>sum[n]) puts("NIE");
else if (f[x]) printf("1 %d\n",f[x]);
else if (len[f[x+1]]>len[1]) printf("%d %d\n",2+len[1],f[x+1]+len[1]);
else if (f[x+1]+len[f[x+1]]<=n) printf("%d %d\n",1+len[f[x+1]],f[x+1]+len[f[x+1]]);
else puts("NIE");
}
return 0;
}

[BZOJ2276][Poi2011]Temperature

之前萝卜考试蒯的原题。在考场上\(YY\)了一个神奇的链表维护\(dp\)的做法。

大致就是说设\(f_i\)表示长度为\(i\)时最后一位的最小值。对于每个\([L,R]\),可以把前缀\(\le L\)的全部改成\(L\),再把后面大于\(R\)的删掉就行了。复杂度\(O(n)\) 。

#include<cstdio>
#include<algorithm>
using namespace std;
int gi(){
int x=0,w=1;char ch=getchar();
while ((ch<'0'||ch>'9')&&ch!='-') ch=getchar();
if (ch=='-') w=0,ch=getchar();
while (ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
return w?x:-x;
}
const int N = 1e6+5;
int n,pre[N],nxt[N],val[N],sz[N],tot,hd,tl,len,ans;
int main(){
n=gi();
for (int i=1;i<=n;++i){
int L=gi(),R=gi(),S=1;
while (hd&&val[hd]<=L) S+=sz[hd],hd=nxt[hd],pre[hd]=0;
if (!hd) val[hd=tl=++tot]=L,sz[tot]=S;
else pre[hd]=++tot,nxt[tot]=hd,val[tot]=L,sz[tot]=S,hd=tot;
++len;
while (tl&&val[tl]>R) len-=sz[tl],tl=pre[tl],nxt[tl]=0;
ans=max(ans,len);
}
printf("%d\n",ans);return 0;
}

[BZOJ2277][Poi2011]Strongbox

首先密码的构成一定是\(0,x,2x,3x...\),其中\(x|n\)。题中要求最大化密码的数量,也就是最小化\(x\)。已知\(x|a_k,x\nmid a_i(1\le i < k)\)。那么\(\gcd(a_i,a_k)(1\le i<k)\)以及它们的因子都不能作为答案。把\(\gcd(n,a_k)\)的所有因子拿出来,根据其内部的拓扑关系递推一下即可。复杂度最低可以做到\(包含的质因子个数O(d(n)\times n\mbox{包含的质因子个数})\) 。

#include<cstdio>
#include<algorithm>
using namespace std;
#define ll long long
ll gi(){
ll x=0,w=1;char ch=getchar();
while ((ch<'0'||ch>'9')&&ch!='-') ch=getchar();
if (ch=='-') w=0,ch=getchar();
while (ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
return w?x:-x;
}
const int N = 250005;
ll n,a[N],d[N],p[N];
int k,tot,cnt,ban[N];
int main(){
n=gi();k=gi();
for (int i=1;i<=k;++i) a[i]=gi();
a[k]=__gcd(a[k],n);
for (ll i=1;i*i<=a[k];++i)
if (a[k]%i==0){
d[++tot]=i;
if (i*i<a[k]) d[++tot]=a[k]/i;
}
sort(d+1,d+tot+1);ll tmp=a[k];
for (ll i=2;i*i<=tmp;++i)
if (tmp%i==0){
p[++cnt]=i;
while (tmp%i==0) tmp/=i;
}
if (tmp>1) p[++cnt]=tmp;
for (int i=1;i<k;++i)
ban[lower_bound(d+1,d+tot+1,__gcd(a[i],a[k]))-d]=1;
for (int i=tot;i;--i)
if (ban[i])
for (int j=1;j<=cnt;++j)
if (d[i]%p[j]==0)
ban[lower_bound(d+1,d+tot+1,d[i]/p[j])-d]=1;
for (int i=1;i<=tot;++i)
if (!ban[i]) return printf("%lld\n",n/d[i]),0;
}

[BZOJ2278][Poi2011]Garbage

一定存在一种方案使得所有的简单环不相交。所以那些不需要改变状态的边是没有用的。

直接\(dfs\),搜到一个环就把它拿出来删掉。注意要加上当前弧优化不然要\(TLE\)。

#include<cstdio>
#include<algorithm>
#include<vector>
using namespace std;
int gi(){
int x=0,w=1;char ch=getchar();
while ((ch<'0'||ch>'9')&&ch!='-') ch=getchar();
if (ch=='-') w=0,ch=getchar();
while (ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
return w?x:-x;
}
const int N = 1e5+5;
const int M = 2e6+5;
int n,m,to[M],nxt[M],head[N],cnt=1,du[N],S[N],ins[N],vis[M],tot;
vector<int>ans[M];
void dfs(int u){
if (ins[u]){
++tot;int x=0;
do x=S[S[0]--],ins[x]=0,ans[tot].push_back(x);while (x^u);
}
for (int &e=head[u];e;e=nxt[e])
if (!vis[e]){
vis[e]=vis[e^1]=1;
ins[S[++S[0]]=u]=1;
dfs(to[e]);
}
}
int main(){
n=gi();m=gi();
for (int i=1;i<=m;++i){
int u=gi(),v=gi();
if (gi()^gi()){
to[++cnt]=v;nxt[cnt]=head[u];head[u]=cnt;
to[++cnt]=u;nxt[cnt]=head[v];head[v]=cnt;
++du[u];++du[v];
}
}
for (int i=1;i<=n;++i) if (du[i]&1) return puts("NIE"),0;
for (int i=1;i<=n;++i) dfs(i);
printf("%d\n",tot);
for (int i=1;i<=tot;++i){
int sz=ans[i].size();printf("%d ",sz);
for (int j=0;j<sz;++j) printf("%d ",ans[i][j]);
printf("%d\n",ans[i][0]);
}
return 0;
}

[BZOJ2280][Poi2011]Plot

二分答案,然后做一个最小圆覆盖。该算法复杂度在期望意义下是\(O(n)\),证明略。

做圆覆盖的时候需要再次二分最大的合法右端点,但是直接二分可能导致复杂度退化,可以先倍增求出最大的\(k\)使得长为\(2^{k-1}\)的区间合法而长为\(2^k\)的区间不合法,再在\([2^{k-1},2^k)\)里二分右端点。

对着Claris代码调的,写得可能有点凌乱哈。

#include<cstdio>
#include<algorithm>
#include<ctime>
#include<cmath>
using namespace std; const int N = 1e5+5;
const double eps = 1e-10; struct node{double x,y;}a[N],b[N],O;
int n,m,len,tim=50,tot,ans[2][N];double R; double dist(node a,node b){
return sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y));
} void work(node A,node B,node C){
double a,b,c,d,e,f;
a=A.x-C.x;b=A.y-C.y;
c=B.x-C.x;d=B.y-C.y;
e=A.x*A.x+A.y*A.y-C.x*C.x-C.y*C.y;
f=B.x*B.x+B.y*B.y-C.x*C.x-C.y*C.y;
O.x=(d*e-b*f)/(2*a*d-2*b*c);
O.y=(a*f-c*e)/(2*a*d-2*b*c);
R=dist(O,A);
} void calc(int l,int r){
len=0;
for (int i=l;i<=r;++i) b[++len]=a[i];
for (int i=1;i<=len;++i) swap(b[i],b[1+rand()%len]);
O=b[1];R=0;
for (int i=1;i<=len;++i)
if (dist(O,b[i])>R+eps){
O=b[i];R=0;
for (int j=1;j<i;++j)
if (dist(O,b[j])>R+eps){
O.x=(b[i].x+b[j].x)/2;
O.y=(b[i].y+b[j].y)/2;
R=dist(b[i],O);
for (int k=1;k<j;++k)
if (dist(O,b[k])>R+eps)
work(b[i],b[j],b[k]);
}
}
}
bool check(double lim){
tot=0;
for (int i=1,j,t,l,r;i<=n;i=j+1){
for (t=1;i+(1<<t)-1<=n;++t){
calc(i,i+(1<<t)-1);
if (R>lim+eps) break;
}
j=i,l=i+(1<<t-1)-1,r=min(n,i+(1<<t)-1);
while (l<=r){
int mid=l+r>>1;calc(i,mid);
if (R<lim+eps) j=mid,l=mid+1;
else r=mid-1;
}
ans[0][++tot]=i;ans[1][tot]=j;
if (tot>m) return false;
}
return true;
} int main(){
scanf("%d%d",&n,&m);srand(20020415);
for (int i=1;i<=n;++i) scanf("%lf%lf",&a[i].x,&a[i].y);
calc(1,n);double l=0,r=R;
if (m>1)
while (tim--&&r-l>eps){
double mid=(l+r)/2;
if (check(mid)) r=mid;else l=mid;
}
check(r);printf("%.8f\n%d\n",r,tot);
for (int i=1;i<=tot;++i) calc(ans[0][i],ans[1][i]),printf("%.8f %.8f\n",O.x,O.y);
return 0;
}

[BZOJ2525][Poi2011]Dynamite

二分答案,然后贪心覆盖就可以了。对每个点维护\(f_i\)表示子树中最近的已覆盖点到\(i\)的距离,\(g_i\)表示子树中最远的未覆盖点到\(i\)的距离,转移讨论一下即可。

#include<cstdio>
#include<algorithm>
using namespace std;
int gi(){
int x=0,w=1;char ch=getchar();
while ((ch<'0'||ch>'9')&&ch!='-') ch=getchar();
if (ch=='-') w=0,ch=getchar();
while (ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
return w?x:-x;
}
const int N = 3e5+5;
const int inf = 1<<30;
int n,m,mrk[N],to[N<<1],nxt[N<<1],head[N],cnt,k,tot,f[N],g[N];
void dfs(int u,int fa){
g[u]=mrk[u]?0:-inf;f[u]=inf;
for (int e=head[u];e;e=nxt[e])
if (to[e]!=fa){
int v=to[e];dfs(v,u);
if (g[u]+f[v]+1<=k) g[u]=-inf;
if (f[u]+g[v]+1>k) g[u]=max(g[u],g[v]+1);
f[u]=min(f[u],f[v]+1);
}
if (g[u]==k) f[u]=0,++tot;
if (f[u]+g[u]<=k) g[u]=-inf;
}
bool check(int mid){
k=mid;tot=0;dfs(1,0);
if (f[1]+g[1]>k) ++tot;
return tot<=m;
}
int main(){
n=gi();m=gi();
for (int i=1;i<=n;++i) mrk[i]=gi();
for (int i=1;i<n;++i){
int u=gi(),v=gi();
to[++cnt]=v;nxt[cnt]=head[u];head[u]=cnt;
to[++cnt]=u;nxt[cnt]=head[v];head[v]=cnt;
}
int l=0,r=n-1,res=0;
while (l<=r){
int mid=l+r>>1;
if (check(mid)) res=mid,r=mid-1;
else l=mid+1;
}
printf("%d\n",res);return 0;
}

[BZOJ2526][Poi2011]Inspection

如果一个点要有答案那就需要满足最大子树的大小不超过\(\frac n2\),而显然这性质只有重心才满足。所以实际上只有至多两个位置有答案其余的都是\(-1\)。

答案显然就是所有点到重心的距离减去最深的深度。注意如果有两个重心,那么当我们处理一个重心的时候,另一个重心所在的子树会比剩下的子树加起来还要大,这时候最深深度就必须在另一个重心所在的子树里面选。

#include<cstdio>
#include<algorithm>
using namespace std;
int gi(){
int x=0,w=1;char ch=getchar();
while ((ch<'0'||ch>'9')&&ch!='-') ch=getchar();
if (ch=='-') w=0,ch=getchar();
while (ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
return w?x:-x;
}
const int N = 1e6+5;
int n,to[N<<1],nxt[N<<1],head[N],cnt,sz[N],w[N],f[N];
long long ans;
void dfs1(int u,int fa){
sz[u]=1;
for (int e=head[u];e;e=nxt[e])
if (to[e]!=fa){
dfs1(to[e],u),sz[u]+=sz[to[e]];
w[u]=max(w[u],sz[to[e]]);
}
w[u]=max(w[u],n-sz[u]);
}
void dfs2(int u,int fa){
sz[u]=1;f[u]=0;
for (int e=head[u];e;e=nxt[e])
if (to[e]!=fa){
dfs2(to[e],u),sz[u]+=sz[to[e]];
f[u]=max(f[u],f[to[e]]+1);
ans+=sz[to[e]];
}
}
int main(){
n=gi();
for (int i=1;i<n;++i){
int u=gi(),v=gi();
to[++cnt]=v;nxt[cnt]=head[u];head[u]=cnt;
to[++cnt]=u;nxt[cnt]=head[v];head[v]=cnt;
}
dfs1(1,0);
for (int i=1;i<=n;++i)
if (w[i]<=(n>>1)){
ans=0;dfs2(i,0);ans<<=1;
if ((~n&1)&&w[i]==(n>>1)){
for (int e=head[i];e;e=nxt[e])
if (sz[to[e]]==(n>>1)) {ans-=f[to[e]]+1;break;}
}else ans-=f[i];
printf("%lld\n",ans);
}else puts("-1");
return 0;
}

[BZOJ2527][Poi2011]Meteors

整体二分模板题。

BZ的神奇数据居然可以卡爆\(\mbox{long long}\)。。。然后我开个\(\mbox{unsigned long long}\)就过了?讲道理这里\(\mbox{unsigned long long}\)也能爆吧。。。

#include<cstdio>
#include<algorithm>
#include<vector>
using namespace std;
int gi(){
int x=0,w=1;char ch=getchar();
while ((ch<'0'||ch>'9')&&ch!='-') ch=getchar();
if (ch=='-') w=0,ch=getchar();
while (ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
return w?x:-x;
}
#define ll unsigned long long
const int N = 300005;
int n,m,k,p[N],st[N],ed[N],ans[N];
struct nation{ll p;int id;}q[N],q1[N],q2[N];
vector<int>a[N];ll val[N],c[N];
void mdf(int k,ll v){while(k<=m)c[k]+=v,k+=k&-k;}
ll qry(int k){ll s=0;while(k)s+=c[k],k^=k&-k;return s;}
void upt(int p,ll f){
if (st[p]<=ed[p])
mdf(st[p],val[p]*f),mdf(ed[p]+1,-val[p]*f);
else
mdf(1,val[p]*f),mdf(ed[p]+1,-val[p]*f),mdf(st[p],val[p]*f);
}
void solve(int L,int R,int l,int r){
if (L>R) return;
if (l==r){
for (int i=L;i<=R;++i) ans[q[i].id]=l;
return;
}
int mid=l+r>>1,t1=0,t2=0;
for (int i=l;i<=mid;++i) upt(i,1);
for (int i=L;i<=R;++i){
ll tmp=0;
for (int j=0,sz=a[q[i].id].size();j<sz;++j)
tmp+=qry(a[q[i].id][j]);
if (tmp>=q[i].p) q1[++t1]=q[i];
else q[i].p-=tmp,q2[++t2]=q[i];
}
for (int i=l;i<=mid;++i) upt(i,-1);
for (int i=L,j=1;j<=t1;++i,++j) q[i]=q1[j];
for (int i=L+t1,j=1;j<=t2;++i,++j) q[i]=q2[j];
solve(L,L+t1-1,l,mid);solve(L+t1,R,mid+1,r);
}
int main(){
n=gi();m=gi();
for (int i=1;i<=m;++i) a[gi()].push_back(i);
for (int i=1;i<=n;++i) q[i]=(nation){gi(),i};
k=gi();
for (int i=1;i<=k;++i) st[i]=gi(),ed[i]=gi(),val[i]=gi();
++k;st[k]=1;ed[k]=m;val[k]=1<<30;
solve(1,n,1,k);
for (int i=1;i<=n;++i)
if (ans[i]==k) puts("NIE");
else printf("%d\n",ans[i]);
return 0;
}

[BZOJ2529][Poi2011]Sticks

拼成三角形的三根木棍一定是按长度排序后相邻的(不考虑颜色的话),所以就从前往后维护三根木棍就好了。

#include<cstdio>
#include<algorithm>
using namespace std;
int gi(){
int x=0,w=1;char ch=getchar();
while ((ch<'0'||ch>'9')&&ch!='-') ch=getchar();
if (ch=='-') w=0,ch=getchar();
while (ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
return w?x:-x;
}
#define fi first
#define se second
const int N = 1e6+5;
int k,n;pair<int,int>a[N],b[3];
int main(){
k=gi();
for (int i=1;i<=k;++i){
int m=gi();
while (m--) a[++n]=make_pair(gi(),i);
}
sort(a+1,a+n+1);
for (int i=1;i<=n;++i){
if (a[i].se==b[0].se) b[0]=a[i];
else if (a[i].se==b[1].se) b[1]=a[i];
else if (a[i].se==b[2].se) b[2]=a[i];
else b[0]=a[i];
sort(b,b+3);
if (b[0].se&&b[0].fi+b[1].fi>b[2].fi)
return printf("%d %d %d %d %d %d\n",b[0].se,b[0].fi,b[1].se,b[1].fi,b[2].se,b[2].fi),0;
}
puts("NIE");return 0;
}

[BZOJ2530][Poi2011]Party

随便选两个未被删掉的点,如果两个点之间没有边,那么就把这两个点都删掉。

因为团的大小有\(\frac 23n\),而删掉的两个点不可能都在团里,所以至少删掉了一个需要删的,那么肯定可以剩下一个大小为\(\frac 13n\)的团。

#include<cstdio>
#include<algorithm>
using namespace std;
int gi(){
int x=0,w=1;char ch=getchar();
while ((ch<'0'||ch>'9')&&ch!='-') ch=getchar();
if (ch=='-') w=0,ch=getchar();
while (ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
return w?x:-x;
}
const int N = 3005;
int n,m,k,a[N][N],del[N];
int main(){
n=gi();m=gi();k=n/3;
for (int i=1,u,v;i<=m;++i)
u=gi(),v=gi(),a[u][v]=a[v][u]=1;
for (int i=1;i<=n&&k;++i)
for (int j=i+1;j<=n&&k;++j)
if (!del[i]&&!del[j]&&!a[i][j])
--k,del[i]=del[j]=1;
k=0;
for (int i=1;i<=n;++i)
if (!del[i]){
printf("%d ",i);
if (++k==n/3) break;
}
puts("");return 0;
}

[BZOJ2557][Poi2011]Programming Contest

直观的想法是直接跑费用流,但是会\(TLE\)。

可以跑匈牙利,按罚时的先后顺序增广每一个点,匈牙利可以保证之前的匹配点不会被改变,所以这样跑是正确的。

注意增广一次的最坏复杂度是\(O(k)\)也就是\(O(nm)\)的,如果对每条边都增广一次复杂度就变成\(O(n^2m^2)\)了。可以对每个点标记一下是否增广失败过,然后如果之前就已经增广失败了这次就没必要再跑了。这样最多成功增广\(m\)次,每个点最多增广失败一次,复杂度就是\(O((n+m)nm)\)。

#include<cstdio>
#include<algorithm>
using namespace std;
int gi(){
int x=0,w=1;char ch=getchar();
while ((ch<'0'||ch>'9')&&ch!='-') ch=getchar();
if (ch=='-') w=0,ch=getchar();
while (ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
return w?x:-x;
}
const int N = 505;
int n,m,r,t,k,cnt,to[N*N],nxt[N*N],head[N],d[N],mat[N],vis[N],tim,ban[N],ans1,ans2;
pair<int,int>a[N*N];
bool dfs(int u){
for (int e=head[u];e;e=nxt[e]){
int v=to[e];
if (vis[v]==tim) continue;vis[v]=tim;
if (!mat[v]||dfs(mat[v])) return mat[v]=u,true;
}
return false;
}
int main(){
n=gi();m=gi();r=gi();t=gi();k=gi();
for (int i=1;i<=k;++i){
int u=gi(),v=gi();
to[i]=v;nxt[i]=head[u];head[u]=i;
if (d[u]<t/r) a[++cnt]=make_pair(++d[u],u);
}
sort(a+1,a+cnt+1);
for (tim=1;tim<=cnt;++tim)
if (!ban[a[tim].second]&&dfs(a[tim].second))
++ans1,ans2+=a[tim].first;
else ban[a[tim].second]=1;
printf("%d %lld\n",ans1,1ll*ans2*r);
return 0;
}