poj 3266 (01分数规划+平衡树维护凸壳)

题意: 

 

Bessie考了N科(N≤50000),每科的得分为Ti,满分为Pi(样例似乎是五科红灯……)。

现在Bessie的老师要给她们统计最终得分,方法 是先算出每一科得分的百分比,去除D个百分比最低的科目,然后剩下科目的∑T / ∑P就是最终得分的百分比。

很幸运,没有人有两科的得分比一样。Bessie的数学很牛X,她马上就发现她的老师在坑爹,因为有时候可以通过去除不同的科 目来获得更高的分数。

现在Bessie想知道,对于哪些D值,她可以通过去除与老师不同的D科,从而获得更高的分数呢?

(摘自http://www.cnblogs.com/perseawe/archive/2012/05/03/01fsgh.html)

 

做法:

 

假设现在取了i个百分比靠前的科目,当前的综合百分比是k。

 

如果能把选的科目p替换成一个没选的科目q使得综合百分比提高 ,根据01分数规划的思想,显然 p.T-k*p.P<q.T-k*q.P 。

 

于是就可以在选的科目中找一个科目 p 使得 p.T-k*p.P最小,再在没选的科目中找一个科目 q 使 q.T-k*q.P 最大 。

 

然后发现 T-k*P 就是向量的点积 (1,-k)*(T,P) ,就可以用平衡树维护两个凸壳来找最大值和最小值。

 

注意到:排序后 选的科目越多,综合百分比越低,因此k是单调的,于是便不需要在平衡树里二分查找了。

 

网上题解说可以用单调栈,但是总感觉有问题,还是用了平衡树。

 

我依旧是把上凸壳转化成下凸壳用同一个函数来做。

#include<cstdio>
#include<map>
#include<algorithm>
#define X first
#define Y second
using namespace std;
typedef long long ll;
typedef map<int,int>::iterator itr;
const int N=50005;
int n;
double k[N],fmax[N],fmin[N];
int ans[N];
struct P
{
	int x,y;
	P operator-(const P &t)const
	{
		return {x-t.x,y-t.y};
	}
	int operator^(P t)
	{
		return x*t.y-y*t.x;
	}
}a[N];
bool cmp(P a,P b)
{
	return 1.0*a.y/a.x>1.0*b.y/b.x;
}
double dot(itr it,double x,double y)
{
	return it->X*x+it->Y*y;
}
ll cross(itr l,P p2,itr r)
{
	P p1={l->X,l->Y},p3={r->X,r->Y};
	return (p2-p1)^(p3-p2);
}
struct hull
{
	map<int,int>s;
	itr mid,l,r,p1,p2;
	void init() { s.clear(); }
	itr pre(itr it)
	{
		if (it==s.end()) return it;
		if (it==s.begin()) return s.end();
		return --it;
	}
	itr suc(itr it)
	{
		if (it==s.end()) return it;
		return ++it;
	}
	bool inside(P p)
	{
		if (s.empty()) return 0;
		r=s.lower_bound(p.x);
		if (r==s.end()) return 0;
		if (r->X==p.x) return p.y>=r->Y;
		if (r==s.begin()) return 0;
		l=r; l--;
		return cross(l,p,r)<=0;
	}
	void add(P p)
	{
		if (inside(p)) return;
		s[p.x]=p.y;
		mid=s.find(p.x);
		p1=suc(mid); p2=suc(p1);
		while(p1!=s.end()&&p2!=s.end()&&cross(mid,{p1->X,p1->Y},p2)<=0)
			s.erase(p1),p1=p2,p2=suc(p2);
		p1=pre(mid); p2=pre(p1);
		while(p1!=s.end()&&p2!=s.end()&&cross(mid,{p1->X,p1->Y},p2)>=0)
			s.erase(p1),p1=p2,p2=pre(p2);
	}
	double get(double x,double y)
	{
		r=s.end(); r--;
		l=pre(r);
		while(l!=s.end()&&dot(l,x,y)<=dot(r,x,y))
			s.erase(r),r=l,l=pre(l);
		return dot(r,x,y);
	}
}G;
int main()
{
	scanf("%d",&n);
	for (int i=1;i<=n;i++) scanf("%d%d",&a[i].y,&a[i].x);
	sort(a+1,a+n+1,cmp);
	double sumT=0,sumP=0;
	for (int i=1;i<=n;i++) sumT+=a[i].y,sumP+=a[i].x,k[i]=sumT/sumP;
	G.init();
	for (int i=1;i<=n;i++)
	{
		G.add(a[i]);
		fmin[i]=G.get(-k[i],1);
	}
	G.init();
	for (int i=n;i>=2;i--)
	{
		G.add({a[i].x,-a[i].y});
		fmax[i-1]=-G.get(k[i-1],1);
	}
	int cnt=0;
	for (int i=1;i<n;i++)
		if (fmin[i]<fmax[i]) ans[++cnt]=n-i;
	printf("%d\n",cnt);
	for (int i=cnt;i>=1;i--) printf("%d\n",ans[i]);
}

hdu 5127(cdq分治+凸包)

参考了这篇博客 http://blog.csdn.net/u013912596/article/details/47978791 

 

我的做法:

其实每次询问就是 找一个向量(x,y)使得 (x,y)*(p,q)最大。根据向量点积的性质很容易发现最优的向量肯定在凸包上。

如果没有删除操作,可以用平衡树来维护上凸壳和下凸壳,每次询问二分查找。

这题有删除操作,看了题解再想了很久才知道怎么分治。

 

对于一个操作区间【L,R】:

如果【L,mid】区间内的添加的向量在 R 之后被删除或者不删,那么就可以用它们构建上下凸壳来更新【mid+1,R】区间内的询问。

如果【mid+1,R】区间内的删除的向量在 L 之前添加,那么也可以用它们构建上下凸壳来更新【L,mid】区间内的询问。

然后就做完了。

 

一些小技巧:下凸壳可以翻转一下变成上凸壳用同样的方法维护,nxt数组记录与操作i相关的另一个操作的位置,可以很方便的判断它是添加操作还是删除操作。

 

#include<cstdio>
#include<map>
#include<algorithm>
using namespace std;
typedef long long ll;
const int N=50005;
const ll INF=0x3f3f3f3f3f3f3f3fll;
int n;
ll ans[N];
int nxt[N];
struct P
{
	int x,y;
	bool operator<(const P &t)const
	{
		return x<t.x||x==t.x&&y>t.y;
	}
	P operator-(const P &t)
	{
		return {x-t.x,y-t.y};
	}
	ll operator*(const P &t)
	{
		return 1ll*x*t.x+1ll*y*t.y;
	}
	ll operator^(const P &t)
	{
		return 1ll*x*t.y-1ll*y*t.x;
	}
}a[N];
ll cross(P a,P b,P c)
{
	return (b-a)^(c-b);
}
map<P,int>mp;
struct hull
{
	int n,top;
	P a[N],stk[N];
	void init()
	{
		n=0;
	}
	void add(P p)
	{
		a[++n]=p;
	}
	void make()
	{
		sort(a+1,a+n+1);
		top=0;
		for (int i=1;i<=n;i++)
		{
			while(top>1&&cross(stk[top-1],stk[top],a[i])>=0) top--;
			stk[++top]=a[i];
		}
	}
	ll get(P p)
	{
		if (top==0) return -INF;
		int l=1,r=top;
		while(l<r)
		{
			int mid=(l+r)/2;
			if (p*stk[mid]<p*stk[mid+1]) l=mid+1;else r=mid;
		}
		return p*stk[l];
	}
}up,down;
void solve(int l,int r)
{
	if (l==r) return;
	int mid=(l+r)/2;

	up.init(); down.init();
	for (int i=l;i<=mid;i++)
		if (nxt[i]>r)
		{
			up.add(a[i]);
			down.add({a[i].x,-a[i].y});
		}
	up.make(); down.make();
	for (int i=mid+1;i<=r;i++)
		if (nxt[i]==i)
		{
			ans[i]=max(ans[i],up.get(a[i])),
			ans[i]=max(ans[i],down.get({a[i].x,-a[i].y}));
		}

	up.init(); down.init();
	for (int i=mid+1;i<=r;i++)
		if (nxt[i]<l)
		{
			up.add(a[i]);
			down.add({a[i].x,-a[i].y});
		}
	up.make(); down.make();
	for (int i=l;i<=mid;i++)
		if (nxt[i]==i)
		{
			ans[i]=max(ans[i],up.get(a[i]));
			ans[i]=max(ans[i],down.get({a[i].x,-a[i].y}));
		}

	solve(l,mid);
	solve(mid+1,r);
}
int main()
{
	while(scanf("%d",&n),n)
	{
		mp.clear();
		for (int i=1;i<=n;i++) ans[i]=-INF;
		for (int i=1;i<=n;i++)
		{
			int tp;
			scanf("%d%d%d",&tp,&a[i].x,&a[i].y);
			if (tp==1) nxt[i]=n+1,mp[a[i]]=i;
			if (tp==-1)
			{
				int id=mp[a[i]];
				nxt[id]=i; nxt[i]=id;
				mp.erase(a[i]);
			}
			if (tp==0) nxt[i]=i;
		}
		solve(1,n);
		for (int i=1;i<=n;i++)
			if (nxt[i]==i) printf("%lld\n",ans[i]);
	}
}

hdu 5412 (动态区间第k小,整体二分)

参考了这篇博客:http://blog.csdn.net/v5zsq/article/details/50775827

树套树感觉好麻烦,就学了下整体二分,发现还是挺简单的。每个询问都进入了第32层,所以复杂度大概是O(nlogn*32)。

#include<cstdio>
#include<algorithm>
using namespace std;
const int N=100005,INF=0x3f3f3f3f;
int c[N];
void add(int x,int y)
{
	for (int i=x;i<N;i+=i&-i) c[i]+=y;
}
int get(int x)
{
	int tmp=0;
	for (int i=x;i;i-=i&-i) tmp+=c[i];
	return tmp;
}
int n,m;
int a[N];
int ans[N];
struct event
{
	int tp,x,y,z,id;
}Q[N*3],q1[N*3],q2[N*3];
int f[N*3];
int tot;
void solve(int l,int r,int head,int tail)
{
	if (head>tail) return; //这句很关键,否则递归次数会变成2*INF次
	if (l==r)
	{
		for (int i=head;i<=tail;i++)
			if (Q[i].tp==3) ans[Q[i].id]=l;
		return;
	}
	int mid=(l+r)/2;
	for (int i=head;i<=tail;i++)
	{
		if (Q[i].tp==1&&Q[i].y<=mid) add(Q[i].x,1);
		if (Q[i].tp==2&&Q[i].y<=mid) add(Q[i].x,-1);
		if (Q[i].tp==3) f[i]=get(Q[i].y)-get(Q[i].x-1);
	}  //对于每个询问搞出区间内不大于mid的个数

	for (int i=head;i<=tail;i++)
	{
		if (Q[i].tp==1&&Q[i].y<=mid) add(Q[i].x,-1);
		if (Q[i].tp==2&&Q[i].y<=mid) add(Q[i].x,1);
	}  //清空树状数组

	int cnt1=0,cnt2=0;
	for (int i=head;i<=tail;i++)
	{
		if (Q[i].tp<3)
		{
			if (Q[i].y<=mid) q1[++cnt1]=Q[i];
			else q2[++cnt2]=Q[i];
		}
		else
		{
			if (f[i]<Q[i].z) Q[i].z-=f[i],q2[++cnt2]=Q[i];
			else q1[++cnt1]=Q[i];
		}
	}//分离询问

	int tmp=0;
	for (int i=1;i<=cnt1;i++) Q[head+tmp]=q1[i],tmp++;
	for (int i=1;i<=cnt2;i++) Q[head+tmp]=q2[i],tmp++;
	solve(l,mid,head,head+cnt1-1);
	solve(mid+1,r,head+cnt1,tail);
}
int main()
{
	while(~scanf("%d",&n))
	{
		tot=0;
		for (int i=1;i<=n;i++)
		{
			scanf("%d",&a[i]);
			Q[++tot]={1,i,a[i],i}; //把一开始的值也当作事件来看待
		}
		scanf("%d",&m);
		for (int i=1;i<=m;i++)
		{
			ans[i]=-INF;
			int tp,x,y,z;
			scanf("%d%d%d",&tp,&x,&y);
			if (tp==1)
			{
				Q[++tot]={2,x,a[x],0,i};
				Q[++tot]={1,x,y,0,i};
				a[x]=y;  //修改就是删除一个数再插入一个数
			}
			else
			{
				scanf("%d",&z);
				Q[++tot]={3,x,y,z,i};
			}
		}
		solve(0,INF,1,tot);
		for (int i=1;i<=m;i++)
			if (ans[i]!=-INF) printf("%d\n",ans[i]);
	}
}

HDU 5803(2016多校第6场) 数位dp

题意:

You are given four integers A , B , C , D , there are many different (a,b,c,d) satisfy a+c>b+d && a+d≥b+c && 0≤a≤A && 0≤b≤B && 0≤c≤C && 0≤d≤D ?

做法:

可以将四个数都拆成2进制,用记忆化搜索来统计。记w1=a+c-b-d,w2=a+d-b-c,如果搜的过程中w1大于2其实可以把它变成2,对答案不影响,如果w1小于2则直接剪枝。w2也同理。

 

(比赛的时候完全没想到数位dp,看了huanzhizun的博客才会做)I good vegetable!a !

 

#include<cstdio>
#include<cstring>
using namespace std;
typedef long long ll;
const int N=64,mo=1e9+7;
ll A,B,C,D;
int a[N],b[N],c[N],d[N];
int f[N][2][2][2][2][5][5];
void add(int &x,int y)
{
	x=(x+y)%mo;
}
void cg(ll x,int *a)
{
	for (int i=0;i<N;i++) a[i]=0;
	int cnt=0;
	while(x) a[++cnt]=x%2,x/=2;
}
int dfs(int pos,bool u1,bool u2,bool u3,bool u4,int w1,int w2)
{
	if (pos==0) return w1>0&&w2>=0;
	int ret=f[pos][u1][u2][u3][u4][w1+2][w2+2];
	if (ret!=-1) return ret;
	ret=0;

	int d1=1,d2=1,d3=1,d4=1;
	if (u1) d1=a[pos];
	if (u2) d2=b[pos];
	if (u3) d3=c[pos];
	if (u4) d4=d[pos];

	for (int i1=0;i1<=d1;i1++)
	for (int i2=0;i2<=d2;i2++)
	for (int i3=0;i3<=d3;i3++)
	for (int i4=0;i4<=d4;i4++)
	{
		int ww1=w1*2+i1+i3-i2-i4;
		int ww2=w2*2+i1+i4-i2-i3;
		if (ww1>2) ww1=2;
		if (ww2>2) ww2=2;
		if (ww1<-2) continue;
		if (ww2<-2) continue;

		bool uu1=u1,uu2=u2,uu3=u3,uu4=u4;
		if (i1<a[pos]) uu1=0;
		if (i2<b[pos]) uu2=0;
		if (i3<c[pos]) uu3=0;
		if (i4<d[pos]) uu4=0;

		add(ret,dfs(pos-1,uu1,uu2,uu3,uu4,ww1,ww2));
	}
	return f[pos][u1][u2][u3][u4][w1+2][w2+2]=ret;
}
int main()
{
	int t; scanf("%d",&t);
	while(t--)
	{
		scanf("%lld%lld%lld%lld",&A,&B,&C,&D);
		cg(A,a); cg(B,b); cg(C,c); cg(D,d);
		memset(f,-1,sizeof(f));
		printf("%d\n",dfs(63,1,1,1,1,0,0));
	}
}

spoj 839 Optimal Marks (最小割)

这是《最小割模型在信息学竞赛中的应用》里的一道例题。

从二进制考虑,每个二进制位都用最小割来求解。

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=505,M=3005,INF=0x3f3f3f3f;
const int mxN=N,mxM=M*4+2*N;
int n,m;
int lx[M],ly[M];
int v[N];
bool mk[N];
int p;
struct graph{
	int S,T;
	int base[mxN],vec[mxM],pre[mxM],tot;
	int c[mxM];
	int d[mxN],q[mxN];
	bool vis[mxN];
	void init()
	{
		memset(base,0,sizeof(base));
		tot=1;
	}
	void link(int x,int y,int z)
	{
		vec[++tot]=y; pre[tot]=base[x]; base[x]=tot; c[tot]=z;
		vec[++tot]=x; pre[tot]=base[y]; base[y]=tot; c[tot]=0;
	}
	void build()
	{
		S=0; T=n+1;
		for (int i=1;i<=m;i++) link(lx[i],ly[i],1),link(ly[i],lx[i],1);
		for (int i=1;i<=n;i++)
		{
			if (!mk[i]) continue;
			if (v[i]&1<<p) link(S,i,INF);
			else link(i,T,INF);
		}
	}
	void update(int u)
	{
		vis[u]=1;
		v[u]|=1<<p;
		for (int now=base[u];now;now=pre[now])
		{
			int v=vec[now];
			if (vis[v]||c[now]==0) continue;
			update(v);
		}
	}
	void work()
	{
		for (int i=S;i<=T;i++) vis[i]=0;
		update(S);
	}
	bool bfs()
	{
		int head=0,tail=0;
		memset(d,-1,sizeof(d));
		d[S]=0;
		q[++tail]=S;
		while(head<tail)
		{
			head++;
			int u=q[head];
			for (int now=base[u];now;now=pre[now])
			{
				int v=vec[now];
				if (d[v]==-1&&c[now]>0)
				{
					d[v]=d[u]+1;
					q[++tail]=v;
					if (v==T) return 1;
				}
			}
		}
		return 0;
	}
	int dfs(int u,int flow)
	{
		int r=0;
		if (u==T) return flow;
		for (int now=base[u];now&&r<flow;now=pre[now])
		{
			int v=vec[now];
			if (c[now]>0&&d[v]==d[u]+1)
			{
				int x=min(c[now],flow-r);
				x=dfs(v,x);
				r+=x;
				c[now]-=x;
				c[now^1]+=x;
			}
		}
		if (!r)d[u]=-2;
		return r;
	}
	int dinic()
	{
		int ans=0;
		while(bfs())
			ans+=dfs(S,INF);
		return ans;
	}
}G;
void read()
{
	scanf("%d%d",&n,&m);
	for (int i=1;i<=n;i++) mk[i]=0,v[i]=0;
	for (int i=1;i<=m;i++) scanf("%d%d",&lx[i],&ly[i]);
	int k; scanf("%d",&k);
	for (int i=1;i<=k;i++)
	{
		int x; scanf("%d",&x);
		mk[x]=1;
		scanf("%d",&v[x]);
	}
}
int main()
{
	int t; scanf("%d",&t);
	while(t--)
	{
		read();
		for (p=0;p<31;p++)
		{
			G.init();
			G.build();
			G.dinic();
			G.work();
		}
		for (int i=1;i<=n;i++) printf("%d\n",v[i]);
	}
}

(Educational Codeforces Round 8) E Zbazi in Zeydabad (树状数组优化暴力)

题目链接:

http://codeforces.com/contest/628/problem/E

 

题意:

问n*m (1<=n,m<=3000) 的矩阵中有多少个由z组成的Z。

 

做法:

先求出每个z向左,向右,向做下延伸的最大距离。

 

然后就是以每个z为右上角,设当前的z的坐标是(X,Y),设len=min(l[X][Y],ld[X][Y]),那么就是要查询从(X,Y)到(X+len-1,Y-len+1)的路径上有多少个坐标(x',y')满足y'+r[x'][y']-1>=Y。

 

思路其实很简单,暴力找的话总复杂度是O(n^3)。我们可以用树状数组优化查询的部分。

 

设路径上的z坐标为(x',y'),我们在(x',y'+r[x'][y']-1)这个位置涂上颜色,那么其实会发现满足条件的z对应的涂色的地方都在一个矩阵里。这个矩阵的x坐标属于【X,X+len-1】,矩阵里被涂色的方块个数就是我们要查询的答案。

 

可以用二维树状数组来求矩阵里的和。不过发现如果从左下角开始一直往右上角依次查询的话,当前点左下部分那些涂色的地方之后就不会用到了,所以建立x坐标的树状数组,把那些没用的点去掉,就可以了。

 

cf的题目质量真的高。

代码:

#include <bits/stdc++.h>
using namespace std;
const int N=3005;
int n,m;
long long ans;
int l[N][N],r[N][N],ld[N][N];//左,右,左下延伸的距离
int c[N];//x坐标的树状数组
char s[N][N];
vector<int>vec[N]; //用来去掉已经没用的涂色方块
void add(int x,int y)
{
	for (int i=x;i<N;i+=i&-i) c[i]+=y;
}
int get(int x)
{
	int tmp=0;
	for (int i=x;i;i-=i&-i) tmp+=c[i];
	return tmp;
}
void cal(int x,int y)
{
	for (int i=0;i<N;i++) c[i]=0,vec[i].clear();
	for (;x>=1&&y<=m;x--,y++)
	{
		if (s[x][y]=='z')
		{
			add(x,1);//(x,y+r[x][y]-1)的位置被涂色
			vec[y+r[x][y]-1].push_back(x);// 在涂色的那一列存下x坐标,等待删除
			ans+=get(x+min(l[x][y],ld[x][y])-1)-get(x-1);//统计查询的矩阵里的涂色块
		}
		for (int i=0;i<vec[y].size();i++) add(vec[y][i],-1); //去掉y所在那一列没用的涂色块
		vec[y].clear();
	}
}
int main()
{
	scanf("%d%d",&n,&m);
	for (int i=1;i<=n;i++) scanf("%s",s[i]+1);
	for (int i=1;i<=n;i++)
	{
		for (int j=1;j<=m;j++)
			if (s[i][j]=='z') l[i][j]=l[i][j-1]+1;
		for (int j=m;j>=1;j--)
			if (s[i][j]=='z') r[i][j]=r[i][j+1]+1;
	}
	for (int i=n;i>=1;i--)
		for (int j=1;j<=m;j++)
		 	if (s[i][j]=='z') ld[i][j]=ld[i+1][j-1]+1;
	for (int i=1;i<=n;i++) cal(i,1); //从边界开始往右上角统计
	for (int j=2;j<=m;j++) cal(n,j); //从边界开始往右上角统计
	printf("%I64d\n",ans);
}

 

SGU 390 Tickets (数位dp好题)

题意:

你有编号为L到R的钞票,钞票的价值就是它的编号的各个位数字之和(比如编号218的价值就是11)。现在有很多人排着队,你要从编号为L的钞票开始一直给同一个人,给完编号x的钞票再给编号x+1的钞票,直到他手上的价值>=k,换下一个人继续给。问最多能给多少个人使他们手上的价值都>=k。

做法:

先考虑按位暴搜,dfs到n+1的时候其实相当于枚举了一个x(L<=x<=R)。这时候需要知道这个x前面已经累加了多少价值,然后把这个x和之前累加起来的价值合并一下。最后退出递归的时候其实就已经把所有的x从L到R都枚举了一遍,并且返回了合并的结果。

最后记忆化一下复杂度就完美了。

讲不清啊,还是看代码吧

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
char sl[30],sr[30];
int n,k;
struct P
{
	ll num,left;
	P()
	{
		num=left=-1;
	}
	P(ll x,ll y)
	{
		num=x; left=y;
	}
	void operator +=(const P &t)
	{
		num+=t.num;
		left+=t.left;
		num+=left/k;
		left%=k;
	}
}f[20][2][2][170][1005];
P dfs(int pos,bool dn,bool up,int dg,int lft) //dg表示枚举到这一位的数字和,lft表示当前还不足k的积累的价值
{
	if (pos>n)
	{
		if ((lft+dg>=k)) return P(1,0);  // 加上它满k了,多余的就没用了
		return P(0,lft+dg);   //加上它不满k,继续积累
	}
	P ret=f[pos][dn][up][dg][lft];
	if (ret.num!=-1) return ret;
	ret=P(0,lft);
	for (int i=0;i<=9;i++)
	{
		if (dn&&i<sl[pos]-'0') continue;
		if (up&&i>sr[pos]-'0') continue;
		bool ddn=dn; if (i>sl[pos]-'0') ddn=0;
		bool uup=up; if (i<sr[pos]-'0') uup=0;
		int ddg=dg+i;
		P tmp=dfs(pos+1,ddn,uup,ddg,ret.left);  //把ret的剩余部分传递到后面那些数
		ret.left=0;  //ret的剩余部分已经传递了,就要清零
		ret+=tmp;
	}
	return f[pos][dn][up][dg][lft]=ret;
}
int main()
{
	scanf("%s%s%lld",sl+1,sr+1,&k);
	n=strlen(sr+1);
	int m=strlen(sl+1);
	for (int i=m;i>=1;i--) sl[i+(n-m)]=sl[i];
	for (int i=1;i<=n-m;i++) sl[i]='0';   //把数字弄成相同长度,前面补零
	printf("%lld\n",dfs(1,1,1,0,0).num);
}
//http://paste.ubuntu.com/18608740/    原始版本

TJOJ 4148 (数位dp)

题意:问1到n(1<=n<=10^8)所有的数,总共有多少个0出现。

做法:

很明显的数位dp

数位dp的题目我发现都可以用暴搜+记忆话过掉,而且写起来超级方便。赛场上第一个AC这道题。

 

暴搜是这么写的

#include<cstdio>
#include<cstring>
using namespace std;
char s[100];
int a[100];
int n;
long long get(int pos,bool up,bool zr,int now)
{
	if (pos>n) return now;
	long long ret=0;
	for (int i=0;i<=9;i++)
	{
		if (up&&i>a[pos]) continue;
		bool uup=up;
		if (i<a[pos]) uup=0;
		bool zzr=zr;
		if (i!=0) zzr=0;
		int nnow=now;
		if (i==0&&!zr) nnow++;
		ret+=get(pos+1,uup,zzr,nnow);
	}
	return ret;
}
int main()
{
	int t;
	scanf("%d",&t);
	while(t--)
	{
		scanf("%s",s+1);
		n=strlen(s+1);
		for (int i=1;i<=n;i++) a[i]=s[i]-'0';
		printf("%lld\n",get(1,1,1,0));
	}
}

加个记忆化复杂度就变成了f数组的复杂度

#include<cstdio>
#include<cstring>
using namespace std;
char s[100];
int a[100];
int n;
long long f[100][2][2][100];
long long get(int pos,bool up,bool zr,int now)
{
	if (pos>n) return now;
	long long ret=f[pos][up][zr][now];
	if (ret!=-1) return ret;
	ret=0;
	for (int i=0;i<=9;i++)
	{
		if (up&&i>a[pos]) continue;
		bool uup=up;
		if (i<a[pos]) uup=0;
		bool zzr=zr;
		if (i!=0) zzr=0;
		int nnow=now;
		if (i==0&&!zr) nnow++;
		ret+=get(pos+1,uup,zzr,nnow);
	}
	return f[pos][up][zr][now]=ret;
}
int main()
{
	int t;
	scanf("%d",&t);
	while(t--)
	{
		scanf("%s",s+1);
		n=strlen(s+1);
		for (int i=1;i<=n;i++) a[i]=s[i]-'0';
		memset(f,-1,sizeof(f));
		printf("%lld\n",get(1,1,1,0));
	}
}

数位dp的套路就是这样,暴搜加记忆化,超级简单。

hdu 4897(树剖好题)

题意:

一棵树,一开始所有边都是白的,3种操作。

1.把a到b路径上的边变色(白变黑,黑变白)

2.把与所有(a到b路径上的点)相连的边都改变颜色(不包括路径上的边)

3.询问a到b路径上有多少黑边

 

做法:

先树链剖分

如果只有操作1,就是普通的线段树维护重链的做法。

但是有操作2,暴力修改所有儿子的话每次都是O(n),于是想到把要不要变色的信息存在父节点,查询的时候查一下父节点是否要求它变色。

 

具体怎么修改就是轻重边分开考虑。先把所有路径上的点都在point线段树里修改。

对于路径上的轻边,因为它父亲被改变了而这条轻边是不用修改的,那就把这条轻边在edge线段树里修改来抵消父亲对它的影响。

对于路径外需要修改的重边,直接在edge线段树上修改。(这样的重边只有logn条)

 

查询的时候,对于路径上的重边,直接在边的线段树上求答案,对于路径上的轻边,还要结合它的父亲来判断它是否是黑的。

 

代码应该还是比较好懂的。

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=100005;
int size[N],top[N],son[N],fa[N],id[N],rd[N],deep[N],n,idn;
int base[N],vec[2*N],pre[2*N],tot;
struct SegTree{
	int f[4*N],sum[4*N];
	bool lazy[4*N];
	void build(int i,int l,int r)
	{
		if (l==r) {sum[i]=1; f[i]=0; lazy[i]=0; return;}
		int mid=(l+r)/2;
		build(i*2,l,mid);
		build(i*2+1,mid+1,r);
		sum[i]=sum[i*2]+sum[i*2+1];
		f[i]=0; lazy[i]=0;
	}
	void pd(int i)
	{
		if (lazy[i])
		{
			lazy[i]=0;
			f[i*2]=sum[i*2]-f[i*2];
			f[i*2+1]=sum[i*2+1]-f[i*2+1];
			lazy[i*2]^=1;
			lazy[i*2+1]^=1;
		}
	}
	void up(int i)
	{
		f[i]=f[i*2]+f[i*2+1];
	}
	void add(int i,int l,int r,int x,int y)
	{
		if (x>y) return;
		if (x<=l&&r<=y)
		{
			f[i]=sum[i]-f[i];
			lazy[i]^=1;
			return ;
		}
		pd(i);
		int mid=(l+r)/2;
		if (x<=mid) add(i*2,l,mid,x,y);
		if (y>mid) add(i*2+1,mid+1,r,x,y);
		up(i);
	}
	int get(int i,int l,int r,int x,int y)
	{
		if (x>y) return 0;
		if (x<=l&&r<=y) return f[i];
		pd(i);
		int tmp=0;
		int mid=(l+r)/2;
		if (x<=mid) tmp+=get(i*2,l,mid,x,y);
		if (y>mid) tmp+=get(i*2+1,mid+1,r,x,y);
		return tmp;
	}
}edg,pnt;
void init()
{
	memset(top,0,sizeof(top));
	memset(son,-1,sizeof(son));
	memset(base,0,sizeof(base));
	tot=idn=0;
}
void link(int x,int y)
{
    tot++;
    vec[tot]=y;  pre[tot]=base[x];  base[x]=tot;
}
void dfs1(int u,int p)
{
	fa[u]=p;
    size[u]=1;
    son[u]=-1;
    for (int now=base[u];now;now=pre[now])
	{
        int v=vec[now];
        if (v==p) continue;
		deep[v]=deep[u]+1;
		dfs1(v,u);
		size[u]+=size[v];
		if (son[u]==-1||size[v]>size[son[u]]) son[u]=v;
    }
}
void dfs2(int u,int p)
{
	top[u]=p;
    id[u]=++idn;
	rd[idn]=u;
    if (son[u]!=-1) dfs2(son[u],p);
    for (int now=base[u];now;now=pre[now])
	{
		int v=vec[now];
		if (v==son[u]||v==fa[u]) continue;
        dfs2(v,v);
    }
}
void cg1(int a,int b)
{
    int f1=top[a]; int f2=top[b];
    while(f1!=f2)
    {
        if (deep[f1]<deep[f2]) swap(f1,f2),swap(a,b);
        int x=id[f1], y=id[a];
        edg.add(1,1,n,x,y);
        a=fa[f1]; f1=top[a];
    }
    if (deep[a]>deep[b]) swap(a,b);
    if (a==b) return;
    int x=id[a], y=id[b];
	edg.add(1,1,n,x+1,y);
}

void cg2(int a,int b)
{
	int f1=top[a]; int f2=top[b];
    while(f1!=f2)
    {
        if (deep[f1]<deep[f2]) swap(f1,f2),swap(a,b);
        int x=id[f1], y=id[a];
        pnt.add(1,1,n,x,y);
        edg.add(1,1,n,x,x);
        if (son[a]!=-1) edg.add(1,1,n,id[son[a]],id[son[a]]);
        a=fa[f1]; f1=top[a];
    }
    if (deep[a]>deep[b]) swap(a,b);
    int x=id[a], y=id[b];
	pnt.add(1,1,n,x,y);
	edg.add(1,1,n,x,x);
	if (son[b]!=-1) edg.add(1,1,n,id[son[b]],id[son[b]]);
}

int query(int a,int b)
{
	int ans=0;
	int f1=top[a]; int f2=top[b];
    while(f1!=f2)
    {
        if (deep[f1]<deep[f2]) swap(f1,f2),swap(a,b);
        int x=id[f1], y=id[a];
        ans+=edg.get(1,1,n,x+1,y);
        int tmp1=edg.get(1,1,n,x,x);
        int z=id[fa[f1]];
        int tmp2=pnt.get(1,1,n,z,z);
        ans+=tmp1^tmp2;
        a=fa[f1]; f1=top[a];
    }
    if (deep[a]>deep[b]) swap(a,b);
    int x=id[a], y=id[b];
	ans+=edg.get(1,1,n,x+1,y);
	return ans;
}

int main()
{
	int t;
	scanf("%d",&t);
    while(t--)
	{
		scanf("%d",&n);
		init();
		edg.build(1,1,n);
		pnt.build(1,1,n);
    	for (int i=1;i<n;i++)
		{
			int x,y;
			scanf("%d%d",&x,&y);
			link(x,y),link(y,x);
		}
    	dfs1(1,1);
    	dfs2(1,1);

    	int q;
    	scanf("%d",&q);
    	while(q--)
		{
			int tp,x,y;
			scanf("%d%d%d",&tp,&x,&y);
			if (tp==1)
			{
				cg1(x,y);
			}
			if (tp==2)
			{
				cg2(x,y);
				pnt.get(1,1,n,1,1);
			}
			if (tp==3)
			{
				printf("%d\n",query(x,y));
			}
		}
	}
}

 

 

hdu3043Number game(dp+dfs)

题意:给你所有1-n的波浪型排列,输出按字典序将这些序列排序后第k小的数列。

思路:可以先预处理出第i个位置是j的上波浪方案数和下波浪方案数。对于每一位,从小到大枚举j,如果当前的总方案数刚好大于等于k,那么答案数列中这个位置一定是j,如此dfs找下去。

因为输入数据有很多的t,一直WA在这个地方,测数据时也发现数据不对劲,问学长要了数据才发现这个坑。

#include<cstdio>
#include<cmath>
#include<iostream>
#include<cstring>
#include<string>
#include<algorithm>
#include<queue>
#include<functional>
using namespace std;
const int N=25;
unsigned long long m,fu[N][N],fd[N][N];
int i,j,k,t,n;
int a[N];
void dfs(int d,long long k,int pre,int tp)
{
    if (d==0) return ;
    long long sum=0;
    int i;
    if (tp==-1)
    {
        for (i=1;i<pre;i++)
        {
            if (sum+fu[d][i]>=k) break;
            sum+=fu[d][i];
        }
    }
    else
    {
        for (i=pre;i<=d;i++)
        {
            if (sum+fd[d][i]>=k) break;
            sum+=fd[d][i];
        }
    }
    a[d]=i;
    dfs(d-1,k-sum,i,-tp);
}
int main()
{
    fd[1][1]=fu[1][1]=1;
    for (i=2;i<=20;i++)
    {
        for (j=1;j<=i;j++)
        {
            for (k=j;k<=i-1;k++) fu[i][j]+=fd[i-1][k];
            for (k=1;k<=j-1;k++) fd[i][j]+=fu[i-1][k];
        }
    }
    while(~scanf("%d",&t))
    	while(t--)
    {
    	scanf("%d%lld",&n,&m);
        if (n>250) n/=0;
        long long sum=0;
        for (i=1;i<=n;i++)
        {
            if (sum+fd[n][i]>=m)
            {
                a[n]=i;
                dfs(n-1,m-sum,i,-1);
                break;
            }
            sum+=fd[n][i];
            if (sum+fu[n][i]>=m)
            {
                a[n]=i;
                dfs(n-1,m-sum,i,1);
                break;
            }
            sum+=fu[n][i];
        }
        for (i=2;i<=n;i++)
            for (j=1;j<i;j++)
                if (a[i]<=a[j]) a[j]++;

        for (i=n;i>1;i--) printf("%d ",a[i]);
        printf("%d\n",a[1]);
    }
}