后院 (组合数+线段判重)

题目描述】

    左下角是(0,0),右上角是(W,H)的网格上,有(W+1)*(H+1)个格点。现在要在格点上找N个不同的点,使得这些点在一条直线上。并且在这条直线上,相邻点之间的距离不小于D。求方案数模1,000,000,000。

【输入格式】

    第一行一个整数T,表示数据组数。

       接下来T行,每行四个整数N,W,H,D,意义如题目描述。

【输出格式】

    T行,每行一个整数表示答案。

【输入输出样例】

backyard.in

backyard.out

6

2 4 4 1

13 36 48 5

5 5 5 1

50 49 49 1

6 5 5 2

10 55 75 5

300

2

88

102

0

490260662

 

【数据规模】

    20%的数据,N,W,H,D≤10。

    50%的数据,W,H,D≤100。

    另20%的数据,N≤5。

    100%的数据,N≤50,W,H,D≤500,T≤20。

 

首先是C(n,k) 的求法

	C[0][0]=1;
	for (int i=1;i<=501;i++)
	{
		C[i][0]=C[i][i]=1;
		for (int j=1;j<=i-1;j++)
			C[i][j]=(C[i-1][j]+C[i-1][j-1])%F;
	}

这个要背……

其次是判重

每次枚举是不要枚举一条直线,而要枚举一条线段(前后端点必取的)

注意要把线段放入点中,有(n-1)*(k-1)条线段,这些线段是为了让每段都大于d的最小点数,注意最小间隔+1的精度为ceil(k=trunc(d/dis-1e-7)+1)

注意在这种有长度限制(>=d)的题中,先把要添的点填上,再算组合数)

这样就没重复了,枚举一条线段的数量 显然为 (w-i+1)*(h-j+1) 注意用Longlong

回到原题,发现这样是无法考虑N=1,即只有一个店的情况,故特判,显然为网格上的所有点(w+1)*(h+1)

还要考虑堆成的线段->但是横纵线是不需要*2的

#include<cstdio>
#include<cstring>
#include<cmath>
#include<cstdlib>
#include<iostream>
#include<functional>
#include<algorithm>
using namespace std;
#define MAXN (50+10)
#define MAXW (500+10)
#define MAXH (500+10)
#define MAXD (500+10)
#define MAXT (20+10)
#define F (1000000000)
#define eps 1e-10
int t,n,w,h,d;
long long C[MAXW][MAXH]={0};
int gcd(int a,int b)
{
	if (b==0) return a;
	else return gcd(b,a%b);
}
int main()
{
	freopen("backyard.in","r",stdin);
	freopen("backyard.out","w",stdout);
	scanf("%d",&t);

	C[0][0]=1;
	for (int i=1;i<=501;i++)
	{
		C[i][0]=C[i][i]=1;
		for (int j=1;j<=i-1;j++)
			C[i][j]=(C[i-1][j]+C[i-1][j-1])%F;
	}

	while (t)
	{
		long long ans=0;
		scanf("%d%d%d%d",&n,&w,&h,&d);


		// 枚举 totnode-2取 ? 时。至少有2个点才不会Re
		if (n==1)
		{
			cout<<(w+1)*(h+1)<<endl;
			t--;
			continue;
		}

		for (int i=0;i<=w;i++)
			for (int j=0;j<=h;j++)	//(0,0)->(x,y)
			{
				if (i==0&&j==0) continue;
				int g=gcd(i,j);
				int totnode=g+1;  //直线上整点数
				if (totnode<n) continue;
				double dis=sqrt(double((i/g)*(i/g)+(j/g)*(j/g)));
				int k=int(trunc(double(d)/dis-eps))+1; //最少的能拼凑>=d的dis段数	eg:d=2 dis=1 k=2	d=2.5 dis=1 k=3
				if (k*(n-1)+1>totnode) continue; //段数n*[...)  +右端点
				ans=(ans+(i==0||j==0?1:2 )*((((w-i+1)*(h-j+1))%F)*(C[totnode-2-(n-1)*(k-1)][n-2]%F)))%F;  //横线 纵线的特判
		//		cout<<ans;

		//		cout<<i<<' '<<j<<' '<<ans<<endl;
			}
		t--;
		printf("%dn",ans);
	}
//	while (1);
	return 0;
}

分割矩阵 (二分范围[L,R))

   分割矩阵

                   (browine.c/cpp/pas)

【问题描述】

    有N*M的一个非负整数矩阵。现在要把矩阵分成A*B块。矩阵先水平地切A-1刀,把矩阵划分成A块。然后再把剩下来的每一块独立地切竖着B-1刀。每块的价值为块上的数字和。求一种方案,使得最小价值块的价值最大。

【输入格式】

    第一行四个整数N,M,A,B。

    接下来N行,每行M个非负整数。代表这个矩阵

【输出格式】

    一个数字。最小价值块的价值。

【样例输入】

5 4 4 2

1 2 21

3 1 1 1

2 0 1 3

1 1 1 1

1 1 11

【样例输出】

    3

 

样例解释见图片

【数据规模】

   1<=A<=N<=500

   1<=B<=M<=500

    其他数字小于4000。

 

 二分的同志请注意了

m=(l+r)/2 <-----这句是永远也枚不到L与R的

#include<cstdio>
#include<cstring>
#include<cmath>
#include<cstdlib>
#include<iostream>
#include<functional>
#include<algorithm>
using namespace std;
#define MAXN (500+10)
#define MAXM (500+10)
#define MAXT (2000000+10)
int n,m,t1,t2,a[MAXN][MAXM],sum[MAXN][MAXM]={0};
bool is_ok(int l,int r,int _m)
{
	int tot=0,p=0;
	for (int i=1;i<=m;i++)
	{
		tot+=sum[r][i]-sum[l-1][i];
		if (tot>=_m) {tot=0;p++;}
	}
	if (p>=t2) return 1;
	else return 0;
}
bool is_ok_(int _m)
{
	int p=0,l=1;
	for (int i=1;i<=n;i++)
	{
		if (is_ok(l,i,_m)) {l=i+1;p++;}
	}
	if (p>=t1) return 1;
	else return 0;
}
int main()
{
	freopen("browine.in","r",stdin);
	freopen("browine.out","w",stdout);
	scanf("%d%d%d%d",&n,&m,&t1,&t2);
	for (int i=1;i<=n;i++)
		for (int j=1;j<=m;j++)
		{
			scanf("%d",&a[i][j]);
			sum[i][j]=sum[i-1][j]+a[i][j];
		}
	/*
	for (int i=1;i<=n;i++)
		for (int j=1;j<=m;j++)
		{
			printf("%d ",sum[i][j]);
		}
	*/
//	cout<<(is_ok_(4));
	int l=1,r=1,ans=0;
	for (int j=1;j<=m;j++) r+=sum[n][j];

	for (int i=1;i<=60;i++)
	{
		int m_=(l+r)/2;
		if (is_ok_(m_)) {l=ans=m_;}
		else r=m_;
	}
	printf("%dn",ans);

//	while (1);
	return 0;
}

 

混合图 (h[u]误写成h[q[u]]……)

                                     混合图

                   (dizzy.c/cpp/pas)

【问题描述】

    有一张N个点,M1条有向边,M2条无向边组成的混合图。询问一个给所有无向边定向的方案。使得最终的图中没有环。保证一定有解。

【输入格式】

    第一行,三个数字N,M1,M2。

    接下来M1+M2行,每行两数字Ai,Bi。表示一条边。

    前M1条是有向边。方向是Ai到Bi。

【输出格式】

    输出M2行,按输出顺序输出为无向边确定的方向。Ai Bi或Bi Ai。

    有多解时输出任意解。

【样例输入】

4 2 3

1 2

4 3

1 3

4 2

3 2

【样例输出】

1 3

2 4

2 3

【数据规模】

    1<=N<=100 000

    1<=M1,M2<=100000

 

拓扑排序即使有重边也成立!

请务必注意哈希表h[u]别多套一个q[u]……

#include<cstdio>
#include<cstring>
#include<cmath>
#include<cstdlib>
#include<iostream>
#include<functional>
#include<algorithm>
using namespace std;
#define MAXN (100000+10)
#define MAXM (100000+10)
int n,m1,m2,indegree[MAXN]={0},head[MAXN],next[MAXM]={0},edge[MAXM]={0},tot=0;
void addedge(int u,int v)
{
	edge[++tot]=v;
	next[tot]=head[u];
	head[u]=tot;
}
int q[MAXN*2];
bool b[MAXN]={0};
void topsort()
{
	int head_=1,tail=0;
	for (int i=1;i<=n;i++)
		if (indegree[i]==0)
		{
			q[++tail]=i;b[i]=1;
		}
	while (head_<=tail)
	{
		int now=q[head_];
		int p=head[now];
		while (p)
		{
			int v=edge[p];
			indegree[v]--;
			if (indegree[v]==0)
			{
				q[++tail]=v;b[v]=1;
			}
			p=next[p];
		}
		head_++;
	}
}
int h[MAXN];
int main()
{
	freopen("dizzy.in","r",stdin);
	freopen("dizzy.out","w",stdout);
	scanf("%d%d%d",&n,&m1,&m2);
	memset(head,0,sizeof(head));
	for (int i=1;i<=m1;i++)
	{
		int u,v;
		scanf("%d%d",&u,&v);
		addedge(u,v);
		indegree[v]++;
	}
	topsort();
	for (int i=1;i<=n;i++)
	{
		h[q[i]]=i;
	//	cout<<q[i]<<' ';
	}
/*
	for (int i=1;i<=n;i++) cout<<q[i]<<' ';
	cout<<endl;
	for (int i=1;i<=n;i++) cout<<h[i]<<' ';
	cout<<endl;
*/
	for (int i=1;i<=m2;i++)
	{
		int u,v;
		scanf("%d%d",&u,&v);
		if (h[u]<h[v]) printf("%d %dn",u,v);
		else printf("%d %dn",v,u);
	}
//	while (1);
	return 0;
}