稳定性(单向图tarjen)

Problem2 稳定性(cp.cpp/c/pas)

【题目描述】

有2*n个装置,其中奇数编号的为供电装置,偶数编号的为用电装置。

第i*2-1个装置通过单向导线第i*2个装置输送能量(它们称为第i对装置)。除此之外还有m条单向导线。

第i对装置是稳定的,当且仅当:直接连接2者的单向导线损坏时,仍然有一个供电方案使得每个供电装置给一个用电装置供电,且每个用电装置只由一个供电装置供电。

求每对装置稳定与否。

【输入格式】

第一行2个整数n,m。

接下来m行,每行2个整数a、b,表示a往b有一条单向导线。保证a为奇数,b为偶数。

【输出格式】

输出n行,若第i对装置是稳定的,输出“~”,否则输出“@”

 

【样例输入】

2 1

3 2

【样例输出】

@

@

【样例输入2】

2 2

3 2

1 4

【样例输出2】

~

~

【数据范围】

对于20%:N<=10

对于40%:N<=100

对于60%:N<=1000

对于100%:N<=100000,M<=2*N

注意Tarjen模板。

Ps:边最多有300000(原来的n条边+另外m条单向边)

#include<cstdio>
#include<algorithm>
#include<functional>
#include<iostream>
#include<cstdlib>
#include<cstring>
#include<cmath>
using namespace std;
#define MAXN (200000+10)
#define MAXM (300000+10)
int edge[MAXM],pre[MAXM],next[MAXM],size=0;
void addedge(int u,int v)
{
	edge[++size]=v;
	next[size]=pre[u];
	pre[u]=size;
}
int t[MAXN],tim=0,dfs[MAXN];
int s[MAXN],tot=0,n,m;
int kind=0,h[MAXN];
bool b[MAXN],numk[MAXN];
void tar(int u)
{
	t[u]=dfs[u]=++tim;b[u]=1;
	s[++tot]=u;
	for (int p=pre[u];p;p=next[p])
	{
		int &v=edge[p];
		if (!b[v]) tar(v),dfs[u]=min(dfs[u],dfs[v]);
		else if (!h[v]) dfs[u]=min(dfs[u],t[v]);
	}
	if (dfs[u]==t[u])
	{
		kind++;
		bool flag=0;
		while (s[tot]!=u) h[s[tot--]]=kind,flag=1;
		h[s[tot--]]=kind;
		numk[kind]=flag;
	}
}
int main()
{
	freopen("cp.in","r",stdin);
	freopen("cp.out","w",stdout);
	scanf("%d%d",&n,&m);
	memset(pre,0,sizeof(pre));
	memset(b,0,sizeof(b));
	memset(h,0,sizeof(h));
	for (int i=1;i<=n;i++) addedge(2*i-1,2*i);
	for (int i=1;i<=m;i++)
	{
		int u,v;
		scanf("%d%d",&u,&v);
		addedge(v,u);
	}
	for (int i=2;i<=2*n;i+=2)
	{
		if (!b[i]) tar(i);
		if (numk[h[i]]) printf("~n");
		else printf("@n");
	}

	return 0;
}

子序列(省略已知值法与挪位得解法)

Problem 1 子序列(subsequence.pas/c/cpp)

【题目描述】

给定一个长度为N(N为偶数)的序列,问能否将其划分为两个长度为N/2的严格递增子序列,

 

【输入格式】

若干行,每行表示一组数据。对于每组数据,首先输入一个整数N,表示序列的长度。之后N个整数表示这个序列。

 

【输出格式】

同输入行数。对于每组数据,如果存在一种划分,则输出“Yes!”,否则输出“No!“。

 

【样例输入】

6 3 1 4 5 8 7

6 3 2 1 6 5 4

【样例输出】

Yes!

No!

【数据范围】

共三组数据,每组数据行数<=50,0 <= 输入的所有数 <= 10^9

第一组(30%):N <= 20

第二组(30%):N <= 100

第三组(40%):N <= 2000

一般青年Dp方案:F[i][j][k][l] 表示前i+j位分为一个长度为ij结尾,一个长度为kl结尾的序列
是否可行(0,1)

省略已知值:观察发现jl中至少有一个为a[i+j]

故可省略其中一位

n=2000必跪

文艺青年Dp方案:

挪位得解:把f[i][j][k]中的k挪出来 

原因:显然i和j不变时,我们希望k越小越好

所以记录min(k),并记录无解情况

O(n^2)

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<functional>
#include<cstdlib>
#include<cstring>
#include<cmath>
using namespace std;
#define MAXN (2000+10)
#define INF (2139062143)
int n,a[MAXN],f[MAXN][MAXN];
int main()
{
	freopen("subsequence.in","r",stdin);
	freopen("subsequence.out","w",stdout);
	while (scanf("%d",&n)!=EOF)
	{
		memset(f,127,sizeof(f));
		for (int i=1;i<=n;i++) scanf("%d",&a[i]);
		f[1][1]=-1;
		for (int i=1;i<n;i++)
		{
			for (int j=1;j<=i;j++)
			{
				if (f[i][j]!=INF)
				{
					if (a[i]<a[i+1]) f[i+1][j+1]=min(f[i+1][j+1],f[i][j]);
					if (f[i][j]<a[i+1])
						f[i+1][i-j+1]=min(f[i+1][i-j+1],a[i]);
				}
			}
		}
		/*
		for (int i=0;i<=n;i++)
		{
			for (int j=0;j<=n;j++)
				printf("%d ",f[i][j]);
			printf("n");
		}
		*/
		if (f[n][n>>1]!=INF) printf("Yes!n");
		else printf("No!n");
	}
	return 0;
}

数位和乘积(高精组合数学)

Problem 3 数位和乘积(digit.cpp/c/pas)

【题目描述】

一个数字的数位和乘积为其各位数字的乘积。求所有的N位数中有多少个数的数位和乘积恰好为K。请注意,这里的N位数是可以有前导零的。比如01,02视为二位数,但是他们的数位和乘积都是0。

 

【输入格式】

一行两个整数N,K

 

【输出格式】

一个行一个整数表示结果。

 

【样例输入】

2 3

【样例输出】

2

【样例输入2】

2 0

【样例输出2】

19

 

【数据范围】

对于20%:N <= 6。

对于50%:N<=16

存在另外30%:K=0。

对于100%:N <= 50,0 <= K <= 10^9。

法1:

k=0时,ans=10^n-9^n

否则就用记忆化搜索填1..9的个数

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<functional>
#include<iostream>
#include<cstdlib>
#include<cmath>
#include<cctype>
using namespace std;
#define MAXN (50+10)
#define F (10000)
int n,k;
struct highn
{
	int len,a[900];
	highn():len(1){memset(a,0,sizeof(a));}
	highn(int x)
	{
		memset(a,0,sizeof(a));
		int i=0;
		while (x)
		{
			a[++i]=x%F;
			x/=F;
		}
		len=i;
	}
	void print()
	{
		printf("%d",a[len]);
		for (int i=len-1;i;i--)
		{
	//		if (a[i]<10000) printf("0");
			if (a[i]<1000) printf("0");
			if (a[i]<100) printf("0");
			if (a[i]<10) printf("0");
			printf("%d",a[i]);
		}

	}
	friend highn operator+(highn& a,highn& b)
	{
		highn c;
		int maxlen=max(a.len,b.len);
		for (int i=1;i<=maxlen;i++)
		{
			c.a[i]=c.a[i]+a.a[i]+b.a[i];
			if (c.a[i]>F)
			{
				c.a[i+1]+=c.a[i]/F;
				c.a[i]%=F;
			}
		}
		c.len=maxlen+1;
		while (!c.a[c.len]&&c.len>1) c.len--;
		return c;

	}
	friend highn operator-(highn& a,highn& b)
	{
		highn c;
		int maxlen=a.len;
		for (int i=1;i<=maxlen;i++)
		{
			c.a[i]+=a.a[i]-b.a[i];
			if (c.a[i]<0)
			{
				c.a[i+1]--;
				c.a[i]+=F;
			}
		}
		c.len=maxlen;
		while (!c.a[c.len]&&c.len>1) c.len--;
		return c;
	}
	friend highn operator*(highn& a,highn& b)
	{
		highn c;
		for (int i=1;i<=a.len;i++)
		{
			for (int j=1;j<=b.len;j++)
			{
				c.a[i+j-1]+=a.a[i]*b.a[j];
				if (c.a[i+j-1]>F)
				{
					c.a[i+j]+=c.a[i+j-1]/F;
					c.a[i+j-1]%=F;
				}
			}
		}
		c.len=a.len+b.len+1;
		while (!c.a[c.len]&&c.len>1) c.len--;
		return c;
	}

};
highn pow2(highn a,int b)
{
	highn c;
	if (!b) return 1;
	if (b%2)
	{
		c=pow2(a,b/2);
		c=c*c;
		c=c*a;
	}
	else
	{
		c=pow2(a,b/2);
		c=c*c;
	}
	return c;
}
int a[11],tot,b[11];
highn ans,C[51][51];
void dfs(int deep,highn rel,int hasget)
{
	if (n<hasget) {return;}
	if (deep==0||n==hasget)
	{
		for (int i=1;i<=9;i++) if (b[i]) return;
		ans=ans+rel;
		return;
	}
	else if (deep==9)
	{
		int m=min(n-hasget,b[3]/2);
		for (int i=0;i<=m;i++)
		{
			b[3]-=i*2;
			dfs(8,rel*C[n-hasget][i],hasget+i);
			b[3]+=i*2;
		}
	}
	else if (deep==8)
	{
		int m=min(n-hasget,b[2]/3);
		for (int i=0;i<=m;i++)
		{
			b[2]-=i*3;
			dfs(6,rel*C[n-hasget][i],hasget+i);
			b[2]+=i*3;
		}
	}
	else if (deep==6)
	{
		int m=min(n-hasget,min(b[2],b[3]));
		for (int i=0;i<=m;i++)
		{
			b[2]-=i;b[3]-=i;
			dfs(4,rel*C[n-hasget][i],hasget+i);
			b[2]+=i,b[3]+=i;
		}
	}
	else if (deep==4)
	{
		int m=min(n-hasget,b[2]/2);
		for (int i=0;i<=m;i++)
		{
			b[2]-=i*2;
			dfs(2,rel*C[n-hasget][i],hasget+i);
			b[2]+=i*2;
		}
	}
	else
	{
		if (b[2]+b[3]+b[5]+b[7]+hasget<=n)
		{
			int f[4]={2,3,5,7};
			for (int i=0;i<4;i++)
			{
				rel=rel*C[n-hasget][b[f[i]]];
				hasget+=b[f[i]];
			}
			ans=ans+rel;
			return ;
		}
	}
}
int main()
{
//	highn a=1254321,b=7612345;
//	highn c=pow(a,3);
//	c.print();
	freopen("digit.in","r",stdin);
	freopen("digit.out","w",stdout);
	scanf("%d%d",&n,&k);
	if (!k)
	{
		highn c=pow2(10,n),d=pow2(9,n);
		highn e=c-d;
		e.print();
	}
	else
	{

		memset(a,0,sizeof(a));
		tot=0;
		C[0][0]=1;
		for (int i=1;i<=n;i++)
		{
			C[i][0]=C[i][1]=1;
			for (int j=1;j<=i;j++)
			{
				C[i][j]=C[i-1][j]+C[i-1][j-1];
			}
		}
//		C[16][10].print();

		for(int i=2;i<=9;i++)
		{
			while (k%i==0)
			{
				a[i]++;
				k/=i;
				tot++;
			}
		}
		memcpy(b,a,sizeof(a));
		if (k==1) dfs(9,1,0);
		ans.print();
	}
	cout<<endl;
	return 0;
}

法2:

f[i][j][k][l][m]表示填到第i个数,2,3,5,7分别用j,k,l,m个的方案数

考虑后面填1..9的情况(显然不可能填0)

Bug:n=50,C(n,m)最大为C(50,25),可用long long.

----

----

法3:

容易发现

k=2^a*3^b*5^c*7^d时才有解

而且5,7取了当且只当数位为5,7的情况.

2-2,4,6,8

3-3,6,9

5-5

7-7

故可只考虑2,3,不考虑5,7

f[i][j]k]表示i位,取了j个2和k个3后的方案数,

因为可以用1填充,

所以答案=∑f[p][j][k]*C(p,j+k)*C(n,a[5])*C(n-a[5],a[7]) (p<=n-a[5]-a[7])