17.10.18

    • 又来水博客了……
    • 上午
      • BZOJ 1040 [ZJOI2008]骑士

由于每颗树上只有一个环,
所以dfs找出环,取出上面相邻的两个点(x,y)
把x,y之间的双向边删除,分别对x,y跑一个dp(因为现在已成树)
然后取出x为起点且不取x的最大值以及 y为起点且不取y的最大值,把两者中的最大值加入ans
   
注意在dfs时要把一个连通块跑完(全部打上vis标记),不要找到环之后就马上退出。

代码:

#include<cstdio>
#include<cstring>
#include<iostream>
#define MAXN 1000005
#define ll long long
using namespace std;
struct edge{
	int to,next;
}e[MAXN*2];
bool del[MAXN*2];
int head[MAXN],val[MAXN],vis[MAXN];
ll f[MAXN][2],ans,now;
int n,ent=2;
void add(int u,int v){
	e[ent]=(edge){v,head[u]};
	head[u]=ent++;
}
void dp(int u,int fa){
	for(int i=head[u];i;i=e[i].next)if(!del[i]){
		int v=e[i].to;
		if(v==fa) continue;
		dp(v,u);
		f[u][0]+=max(f[v][1],f[v][0]);
		f[u][1]+=f[v][0];
	}
	f[u][1]+=val[u];
}
void enter(int u){
	memset(f,0,sizeof(f));
	dp(u,0);
	now=max(now,f[u][0]);
}
void dfs(int u,int from){
	vis[u]=1;
	for(int i=head[u];i;i=e[i].next)if((i^from)!=1){
		int v=e[i].to;
		if(vis[v]==1){
			now=0; del[i]=del[i^1]=1;
			enter(u);
			enter(v);
			ans+=now;
		}
		else if(!vis[v]) dfs(v,i);
	}
	vis[u]=2;
}
int main(){
	scanf("%d",&n);
	for(int i=1,j;i<=n;i++){
		scanf("%d%d",&val[i],&j);
		add(i,j); add(j,i);	
	}
	for(int i=1;i<=n;i++)
		if(!vis[i]) dfs(i,0);
	printf("%lld",ans);
	return 0;
}
      • Ztraveler继续选讲,很绝望
    • 下午
      • BZOJ 1041 [HAOI2008]圆上的整点

看大佬题解分析:http://hzwer.com/1457.html
另外在补充一个当时自己的疑点:
如果找到一组合法的(A,B),那是否只对应唯一一组(x,y)(x>0,y>0)
   
由 A=(R-x)/d B=(R+x)/d
假设存在x=x1和x=x2(x1!=x2) 都满足上式的那一组(A,B)(即假设该(A,B)对应两组解)
令    C1=R-x1 D1=R+x1 d1=gcd(C1,D1)
      C2=R-x2 D2=R+x2 d2=gcd(C2,D2)
显然 C1+D1=C2+D2=2R ★
又∵C1/d1=A C2/d2=A 即 C1/d1=C2/d2 ①
   D1/d1=B D2/d2=B 即 D1/d1=D2/d2 ②
∴①+ ②:(C1+D1)/d1=(C2+D2)/d2
由于 ★
∴上式 : 2R/d1=2R/d2
∴d1=d2 ③
将 ③带入 ①②得 C1=C2 D1=D2
∴x1=x2,两个解相同。
即不存在两个不同的解满足找到一组合法的 (A,B)
即一组(A,B)只唯一对应一组 (x,y)
所以找到一组合法的 (A,B) 后 ans+=1

代码:

#include<cstdio>
#include<cmath>
#include<iostream>
#define ll long long
using namespace std;
ll r,ans;
int gcd(ll a,ll b){
	while(a^=b^=a^=b%=a);
	return b;
}
bool check(ll a, double B){
	if(B!=floor(B)) return 0;
	ll b=floor(B);
	return a!=b&&gcd(a*a,b*b)==1;
}
int main(){
	scanf("%lld",&r);
	for(int d=1;d<=sqrt(2*r);d++){
		if((2*r)%d) continue;
		for(int a=1;a<=sqrt(r/d);a++){
			double b=sqrt(2*r/d-a*a);
			if(check(a,b)) ans++;
		}
		if(2*r/d==d)continue;
		for(int a=1;a<=sqrt(d/2);a++){
			double b=sqrt(d-a*a);
			if(check(a,b)) ans++;
		}
	}
	printf("%lld",ans*4+4);
	return 0;
}
      • BZOJ 1044 [HAOI2008]木棍分割

先二分出答案,
然后跑dp
令 dp[j][i]表示枚举到了第i根,并且分成了j段的方案数
易得 dp[j][i]= ∑dp[j-1][k] (且 第k+1段到第i段的长度和小于l)
显然 可取的dp值为连续的一段,用队列维护(滑动窗口吧)那一段的dp值,然后直接转移,就不用枚举k了
另外还要滚动数组

代码:

#include<cstdio>
#include<cstring>
#include<iostream>
#define MAXN 50005
using namespace std;
const int mod=10007;
int len[MAXN],dp[2][MAXN];
int n,m,anslen,ansnum; 
void Readin(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++) scanf("%d",&len[i]);
}
bool Check(int limit){
	int k=0,sum=0;
	for(int i=1;i<=n+1;i++){
		if(i==n+1||sum+len[i]>limit) k++,sum=0;
		sum+=len[i];
	}
	return k<=m+1;
}
void Binary(){
	int l=0,r=0,mid;
	for(int i=1;i<=n;i++) l=max(l,len[i]),r+=len[i];
	while(l<=r){
		mid=(l+r)>>1;
		if(Check(mid)) anslen=mid,r=mid-1;
		else l=mid+1;
	}
	printf("%d ",anslen);
}
void DP(){
	int sumlen,sumdp,l,ans=0,*x=dp[0],*y=dp[1];
	x[0]=1;
	for(int j=1;j<=m+1;j++){
		swap(x,y); x[0]=0;
		l=0;sumlen=0; sumdp=y[0];
		for(int i=1;i<=n;i++){
			sumlen+=len[i];
			while(sumlen-len[l]>anslen){
				sumlen-=len[l];
				sumdp=(sumdp-y[l]+mod)%mod;
				l++;
			}
			x[i]=sumdp;
			sumdp=(sumdp+y[i])%mod;
		}
		ans=(ans+x[n])%mod;
	}
	printf("%d",ans);
}
int main(){
	Readin();
	Binary();
	DP(); 
	return 0;
}
      • BZOJ 1045 [HAOI2008] 糖果传递

蓝皮书原题,
数据小时可以用费用流做(负载平衡问题)(好像更麻烦诶)
数据大时就只能按书上的方法做了。

代码:

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define ll long long
#define MAXN 1000005
using namespace std;
ll A[MAXN],C[MAXN],M,mid,ans;
int n;
ll Abs(ll x){
	return x>0?x:-x;
}
int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++) scanf("%lld",&A[i]),M+=A[i];
	M/=n; C[1]=0;
	for(int i=2;i<=n;i++)
		C[i]=C[i-1]+A[i-1]-M;
	sort(C+1,C+n+1);
	mid=C[n/2+1];
	for(int i=1;i<=n;i++) ans+=Abs(mid-C[i]);
	printf("%lld",ans);
	return 0;
}
      • BZOJ 1046 [HAOI2007]上升序列

先用nlogn的算法求出f[i]表示以i位置开始的最长上升子序列的长度
(反着求最长下降子序列)
然后就贪心了:
对于输入的len,从前往后枚举每个位置,
如果这个位置的f值大于等于len,那么该位置必将是在答案中
然后记下这个位置的值,同时len--,继续上面的操作直到len=0;

代码:

#include<cstdio>
#include<cstring>
#include<iostream>
#define MAXN 10005 
using namespace std;
int A[MAXN],f[MAXN],nxt[MAXN];
int tmp[MAXN],ANS[MAXN];
int n,m,len,cnt;
int binary(int *C,int r,int val){
	int l=1,mid,ans;
	while(l<=r){
		mid=(l+r)>>1;
		if(C[mid]<=val) ans=mid,r=mid-1;
		else l=mid+1;
	}
	return ans;
}
void LIS(){
	cnt=0;
	for(int i=n;i>=1;i--){
		if(i==n||A[i]<tmp[cnt]){
			tmp[++cnt]=A[i];
			f[i]=cnt; 
		}
		else{
			int l=binary(tmp,cnt,A[i]);
			tmp[l]=A[i];
			f[i]=l;
		}
	}
}
void solve(int l){
	int p=0;
	for(int i=1;i<=n&&l;i++)
		if(f[i]>=l&&A[i]>ANS[p]){
			ANS[++p]=A[i];
			l--;
		}
	for(int i=1;i<p;i++) printf("%d ",ANS[i]);
		printf("%d",ANS[p]);
}
int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++) scanf("%d",&A[i]);
	LIS();
	scanf("%d",&m);
	while(m--){
		scanf("%d",&len);
		if(len<=cnt) solve(len);
		else printf("Impossible");
		printf("
");
	}
	return 0;
}
    • 晚上
      • BZOJ 1047 [HAOI2007]理想的正方形

单调队列维护
(本来想写写二维RMQ的,但无奈空间不够、、、)

代码:

#include<cstdio>
#include<cstring>
#include<iostream>
#define node(a,b) (node){a,b}
using namespace std;
struct node{
	int val,pos;
}qmax[1005],qmin[1005];
int colmax[1005][1005],colmin[1005][1005],mp[1005][1005];
int A,B,N,lmax,lmin,rmax,rmin,ans=0x3f3f3f3f;
void read(int &x){
	static int f; static char ch;
	x=0; f=1; ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-') f=-1;ch=getchar();}
	while('0'<=ch&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
	x=x*f;
}
int main(){
	read(A); read(B); read(N);
	for(int i=1;i<=A;i++)
		for(int j=1;j<=B;j++)
			read(mp[i][j]);
	for(int j=1;j<=B;j++){
		lmax=lmin=1;rmax=rmin=0;
		for(int i=1;i<=A;i++){
			while(lmin<=rmin&&mp[i][j]<qmin[rmin].val) rmin--;
			qmin[++rmin]=node(mp[i][j],i);
			while(lmin<=rmin&&qmin[lmin].pos+N-1<i) lmin++;
			colmin[i][j]=qmin[lmin].val;
			
			while(lmax<=rmax&&mp[i][j]>qmax[rmax].val) rmax--;
			qmax[++rmax]=node(mp[i][j],i);
			while(lmax<=rmax&&qmax[lmax].pos+N-1<i) lmax++;
			colmax[i][j]=qmax[lmax].val;
		}
	}	
	for(int i=N;i<=A;i++){
		lmax=lmin=1;rmax=rmin=0;
		for(int j=1;j<=B;j++){
			while(lmin<=rmin&&colmin[i][j]<qmin[rmin].val) rmin--;
			qmin[++rmin]=node(colmin[i][j],j);
			while(lmin<=rmin&&qmin[lmin].pos+N-1<j) lmin++;
			
			while(lmax<=rmax&&colmax[i][j]>qmax[rmax].val) rmax--;
			qmax[++rmax]=node(colmax[i][j],j);
			while(lmax<=rmax&&qmax[lmax].pos+N-1<j) lmax++;
			
			if(j>=N) ans=min(ans,qmax[lmax].val-qmin[lmin].val);
		} 
	} 
	printf("%d",ans);
	return 0;
}
      • BZOJ 1048 [HAOI2007]分割矩阵

记忆化搜索
dp[x1][y1][x2][y2][m] 表示在左上角为(x1,y1)右下角为(x2,y2)的矩形中砍m次的最小值((val-ave)^2)

代码:

#include<cmath>
#include<cstdio>
#include<cstring>
#include<iostream>
using namespace std;
int mp[12][12],sum[12][12];
bool vis[12][12][12][12][12];
double dp[12][12][12][12][12],ave;
int a,b,n,tot;
double dfs(int x1,int y1,int x2,int y2,int m){
	if(vis[x1][y1][x2][y2][m]) return dp[x1][y1][x2][y2][m];
	vis[x1][y1][x2][y2][m]=1;
	double &ret=dp[x1][y1][x2][y2][m];
	ret=1e9;
	if(!m){
		ret=sum[x2][y2]-sum[x1-1][y2]-sum[x2][y1-1]+sum[x1-1][y1-1];
		ret=(1.0*ret-ave)*(1.0*ret-ave);
		return ret;
	}
	for(int k=0;k<m;k++)
		for(int i=x1;i<x2;i++)
			ret=min(ret,dfs(x1,y1,i,y2,k)+dfs(i+1,y1,x2,y2,m-1-k));
	for(int k=0;k<m;k++)
		for(int j=y1;j<y2;j++)
			ret=min(ret,dfs(x1,y1,x2,j,k)+dfs(x1,j+1,x2,y2,m-1-k));
	return ret;
}
int main(){
	scanf("%d%d%d",&a,&b,&n);
	for(int i=1;i<=a;i++)
		for(int j=1;j<=b;j++){
			scanf("%d",&mp[i][j]);
			tot+=mp[i][j];
			sum[i][j]=sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1]+mp[i][j];
		}
	ave=1.0*tot/n;
	double ans=dfs(1,1,a,b,n-1);
	printf("%.2lf",sqrt(ans/n));
	return 0;
}
      • BZOJ 1049 [HAOI2006]数字序列

对于第一问, 权值减下标后跑一个nlogn 的最长不降子序列
然后第二问就很绝望了,网上的证明都不太清楚(反正我没看懂,只能感性理解了)。。。
用g[i]表示1-i的序列修改的最小代价。
结论是 对于 j,i 如果 f[j]+1=f[i] 且 b[j]<=b[i](b为新数列,即权值减下标后的数列) 的话
则区间(j,i)的最优修改方法是:存在一个k(j<=k<i)使得b[j]~b[k]等于b[j],b[k+1]~b[i]等于b[i]
(由于是随机数据,看似最坏情况下n^2的复杂度不会超时)

代码:

#include<cstdio>
#include<vector>
#include<cstring>
#include<iostream>
#include<algorithm>
#define MAXN 35005
#define ll long long
#define INF (1ll<<50)
using namespace std;
ll a[MAXN],b[MAXN],prefix[MAXN],suffix[MAXN],tmp[MAXN],f[MAXN],g[MAXN];
int cnt,n;
vector<int>w[MAXN];
ll Abs(ll x){
	return x>0?x:-x;
}
int main(){
	//为了方便处理,把b序列的左边加一个-INF,右边加一个+INF。
	scanf("%d",&n);b[0]=-INF;
	memset(g,0x3f,sizeof(g));
	for(int i=1;i<=n;i++) scanf("%lld",&a[i]),b[i]=a[i]-i; 
	a[++n]=INF;b[n]=a[n]-n; 
	for(int i=1,l;i<=n;i++){
		l=upper_bound(tmp+1,tmp+cnt+1,b[i])-tmp;
		tmp[f[i]=l]=b[i];	
		if(l>cnt) cnt=l;
	}
	printf("%d
",n-cnt); 
	w[0].push_back(0); g[0]=0;
	for(int i=1;i<=n;i++){
		w[f[i]].push_back(i);
		for(int p=0,sz=w[f[i]-1].size();p<sz;p++){
			int j=w[f[i]-1][p];
			if(b[j]>b[i]) continue;
			for(int k=j;k<=i;k++) prefix[k]=prefix[k-1]+Abs(b[k]-b[j]);
			for(int k=i;k>=j;k--) suffix[k]=suffix[k+1]+Abs(b[i]-b[k]);
			for(int k=j;k<=i;k++)
				g[i]=min(g[i],g[j]+prefix[k]-prefix[j]+suffix[k+1]-suffix[i]);
		}
	}
	printf("%lld",g[n]);
	return 0;
}
原文地址:https://www.cnblogs.com/zj75211/p/7689355.html