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
",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} eq0)

所以只需要维护前缀(s_{l,a}-s_{l,b})的最小值即可,每加一个字符至多只会使(26)个最小值发生改变,但因为(s_{r,b}-s_{l,b} eq0)的这个烦人的限制,所以还需要记录一下更新最小值的位置。复杂度(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
",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
",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
",(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
",f[x]);
		else if (len[f[x+1]]>len[1]) printf("%d %d
",2+len[1],f[x+1]+len[1]);
		else if (f[x+1]+len[f[x+1]]<=n) printf("%d %d
",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
",ans);return 0;
}

[BZOJ2277][Poi2011]Strongbox

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

#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/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
",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
",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
%d
",r,tot);
	for (int i=1;i<=tot;++i) calc(ans[0][i],ans[1][i]),printf("%.8f %.8f
",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
",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
",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
",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
",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
",ans1,1ll*ans2*r);
	return 0;
}
原文地址:https://www.cnblogs.com/zhoushuyu/p/9741916.html