2022.9.23测试

一测 \(60pts\),二测 \(200pts\),就离谱。

T1:P3216 [HNOI2011]数学作业(蓝)(一测 \(60\),二测 \(100\)

T2:P3213 [HNOI2011]勾股定理(紫,NP问题,不准备改(最后还是改了……))(\(0\)

T3:P3211 [HNOI2011]XOR和路径(紫)(\(0\)

T4:P3166 [CQOI2014]数三角形(紫)(一测 \(0\),二测 \(100\)

T1:

考试时第一次没给 \(t\) 取模……

可以推出暴力式子:

\(num=num\times 10^{\log_{10}(i)+1}+i\hspace{0.1cm}\text{其中 i 为从小到大枚举的数}\)

对于当 \(1\le i\le9\) 时指数为 \(1\),乘数固定为 \(10\),那么就转换成了:

\(num=num\times 10+i\)

这,不就是矩阵快速幂吗……

建立两个矩阵。

初始矩阵:

\[\begin{pmatrix} num\\ i\\ 1 \end{pmatrix}\]

转移矩阵:

\[\begin{pmatrix} \log_{10}(i)&1&0\\ 0&1&1\\ 0&0&1 \end{pmatrix}\]

进行矩阵快速幂即可。

复杂度:\(O(\log(n))。\)

#include<iostream>
#include<cstdio>
#define int long long
using namespace std;
int n,mod;
struct matrix
{
	int x,y;
	int a[4][4];
	void init()
	{
		for(int i=1;i<=3;i++)for(int j=1;j<=3;j++)a[i][j]=0;
	}
};
matrix operator *(matrix fi,matrix se)
{
	matrix th;
	th.init();
	th.x=fi.x;
	th.y=se.y;
	for(int i=1;i<=fi.x;i++)
	{
		for(int j=1;j<=se.y;j++)
		{
			for(int k=1;k<=fi.y;k++)
			{
				th.a[i][j]+=fi.a[i][k]*se.a[k][j];
				th.a[i][j]%=mod;
			}
		}
	}
	return th;
}
matrix quick_pow(matrix x,int y)
{
	matrix sum,num=x;
	sum.init(),sum.x=sum.y=3;
	for(int i=1;i<=3;i++)sum.a[i][i]=1;
	while(y)
	{
		if(y&1)sum=sum*num;
		num=num*num;
		y>>=1;
	}
	return sum;
}
signed main()
{
	//freopen("math.in","r",stdin);
	//freopen("math.out","w",stdout);
	scanf("%lld%lld",&n,&mod);
	int lim=1,t=1,m=1;
	matrix x;
	x.x=3;
	x.y=1;
	x.a[1][1]=0,x.a[3][1]=1,x.a[2][1]=1;
	while(n>=lim)
	{
		lim*=10;
		t*=10;
		t%=mod;
		matrix y;
		y.init();
		y.x=y.y=3,y.a[1][1]=t,y.a[1][2]=y.a[2][2]=y.a[2][3]=y.a[3][3]=1;
		y=quick_pow(y,min(n-m+1,lim-m));
		x=y*x;
		m*=10;
		if(lim==1000000000000000000ll&&n==1000000000000000000ll)
		{
			t*=10,t%=mod;
			x.a[1][1]=x.a[1][1]*t+1e18;
			x.a[1][1]%=mod;
			break;
		}
	}
	printf("%lld",x.a[1][1]);
	return 0;
}

T2:

据说是NP问题。

很明显我们要先预处理出来勾股数对。

但由于数过于大,所以常规的枚举是解决不了问题的。

但也貌似没有什么很好的办法可以立马找到一个数的勾股数对。

所以只能缩减枚举范围。

已知:

\[\begin{aligned} (x-y)^2+4xy&=(x+y)^2\\ (x^2-y^2)^2+4x^2y^2&=(x^2+y^2)^2\\ (x^2-y^2)^2+(2xy)^2&=(x^2+y^2)^2 \end{aligned}\]

所以可以把 \(A\) 看成 \((x-y)\)\(B\) 看成 \(2xy\),然后枚举 \(i,j\),代表式子中的 \(x,y\),即可缩小枚举范围。因为要满足 \(x^2-y^2\le1e6\hspace{0.3cm}2xy\le1e6\)。枚举复杂度约为 \(O(n)\)

然后对于每一对勾股数对,连接一条边,即可得到一个森林。

这样就可以进行树形dp了。

\(dp_{i,(0,1)}\) 代表选与不选数 \(i\) 的方案数。

如果不选,方案即为自己儿子所有方案的乘积

\[dp_{i,0}=\prod_{(i,j)} dp_{j,0}+dp_{j,1} \]

若选,则自己儿子不能选,乘上自己儿子不选的方案数与选这个数的子集数(不算空集)即可。

\[dp_{i,1}=2^{num_i}\prod_{(i,j)}dp_{j,0} \]

然后对每棵树树形dp即可。

复杂度 \(O(n)\)

但是交上去不对,那是因为在这之前的所有假设都是基于建出来的是棵树。

那接下来就麻烦了。

但是通过楼上大佬的模拟,发现在本题的数据范围内建出来的仅仅是棵仙人掌,这样NP问题就可以转换为普通问题了,那么我们就可以进行仙人掌dp。

首先利用Tarjan思想,对于每个整棵仙人掌遍历一遍,建立时间戳,寻找环。

找到环以后,我们考虑断掉环上的一条边,并记录下来边两边的点。

然后对于每一棵仙人掌,对记录下来的点的状态进行搜索,然后在搜索终端判断两边的点是否同为 \(1\)。如果满足都不同为 \(1\),进行一次树形dp。

那么这棵仙人掌所带来的方案数即为 \(dp_{i,0}+dp_{i,1}\)

最后把所有仙人掌所得来的答案相乘,减去全部为空集情况,即减去一,就可得到答案。

#include<iostream>
#include<cstdio>
#include<vector>
#define int long long
using namespace std;
const int N=1e6+5;
const int mod=1e9+7;
int n,num[N],p[N],vis[N],ins[N],dfn[N],cnt,dp[N][2],chose[N],col[N],color;
vector<int>a[N],t;
int gcd(int x,int y)
{
	if(y==0)return x;
	return gcd(y,x%y);
}
void insert(int x)//记录断掉的边两边的点。
{
	if(!vis[x])
	{
		vis[x]=1;
		t.push_back(x);
	}
}
void build(int x,int fa)//找环。
{
	dfn[x]=++cnt;
	int len=a[x].size();
	for(int i=0;i<len;i++)
	{
		if(a[x][i]==fa)continue;
		if(!dfn[a[x][i]])build(a[x][i],x);
		else if(dfn[a[x][i]]<dfn[x])insert(a[x][i]),insert(x);
	}
}
bool check(int x,int fa)//判断断掉的边的两点是否符合条件
{
	col[x]=color;
	int len=a[x].size();
	for(int i=0;i<len;i++)
	{
		if(a[x][i]==fa)continue;
		if(chose[x]&&chose[a[x][i]])return false;
		if(col[a[x][i]]!=color&&!check(a[x][i],x))return false;
	}
	return true;
}
void get_ans(int x,int fa)//树形dp。
{
	col[x]=color;
	int len=a[x].size();
	dp[x][0]=1;
	dp[x][1]=p[num[x]]-1;
	if(vis[x])
	{
		if(chose[x])dp[x][0]=0;
		else dp[x][1]=0;
	}
	for(int i=0;i<len;i++)
	{
		if(a[x][i]==fa||col[a[x][i]]==color)continue;
		if(col[a[x][i]]!=color)get_ans(a[x][i],x);
		dp[x][1]*=(dp[a[x][i]][0]);
		dp[x][1]%=mod;
		dp[x][0]*=(dp[a[x][i]][0]+dp[a[x][i]][1]);
		dp[x][0]%=mod;
	}
}
int dfs(int x,int step)//搜索。
{
	if(step==t.size())
	{
		color++;
		if(check(x,0))
		{
			color++;
			get_ans(x,0);
			return dp[x][0]+dp[x][1];
		}
		return 0;
	}
	chose[t[step]]=0;
	int sum=0;
	sum+=dfs(x,step+1);
	chose[t[step]]=1;
	sum+=dfs(x,step+1);
	return sum%mod;
}
int solve(int x)
{
	t.clear();
	build(x,0);
	int sum=dfs(x,0);
	return sum;
}
signed main()
{
	scanf("%lld",&n);
	for(int i=1,x;i<=n;i++)scanf("%lld",&x),num[x]++;
	p[0]=1;
	for(int i=1;i<=(N-5);i++)p[i]=p[i-1]*2,p[i]%=mod;
   //以下为预处理。
	for(int i=1;i<=(N-5)/2;i++)
	{
		for(int j=i+1;2*i*j<=(N-5)&&j*j-i*i<=(N-5);j++)
		{
			if(((i&1)^(j&1))&&gcd(i,j)==1)
			{
				int u=j*j-i*i,v=2*i*j;
				if(!num[u]||!num[v])continue;
				a[u].push_back(v);
				a[v].push_back(u);
			}
		}
	}
	int ans=1;
	for(int i=1;i<=N-5;i++)if(num[i]&&!col[i])ans=ans*solve(i)%mod;
	printf("%lld",ans-1);
	return 0;
}

T3:

很明显期望 dp。

开始时想到设 \(f_i\)\(i\)\(n\) 的期望值。

但由于这题是异或,所以要拆分成 \(2\) 进制,分成每一位的子任务解决。

那么改变定义,设 \(f_i\) 为点 \(i\)\(n\)\(x\) 位为 \(1\) 的期望值,\(r_i\) 代表 \(i\) 号点的出度。对于每一条边 \((i,j)\) 那么:

\[f_i=\frac{1}{r_i}(\sum_{(i,j)=0}f_j+\sum_{(i,j)=1}(1-f_j)) \]

\[r_if_i-\sum_{(i,j)=0}f_v+\sum_{(i,j)=1}f_v=\sum_{(i,j)=1}1 \]

那么我们可以把 \(f_i\) 看成未知数,先建方程,再解方程即可。

但是 \(n\) 个未知数,\(n-1\) 个方程,所以还有一个初始方程 \(f_n=0\)

很好理解,因为到了点 \(n\) 以后无法再移动,所以点 \(n\)\(n\) 期望值赋为 \(0\)

答案就是起始点 \(1\) 的期望值乘上对应的位数,即 \(\sum f_1\times 2^{x-1}\)

复杂度:\(O(n^3)\)

#include<iostream>
#include<cstdio>
#include<vector>
#include<cstring>
#define int long long
using namespace std;
const int N=105;
int n,m,cnt,r[N];
double ans[N],t[N][N];
struct node
{
	int to,data;
};
vector<node>a[N];
inline int tabs(int x)
{
	return x>0?x:-x;
}
void build(int x)
{
	memset(t,0,sizeof(t));
	t[n][n]=1;
	for(int i=1;i<n;i++)
	{
		t[i][i]=r[i];
		int len=a[i].size();
		for(int j=0;j<len;j++)
		{
			if(a[i][j].data&x)t[i][a[i][j].to]++,t[i][n+1]++;
			else t[i][a[i][j].to]--;
		}
	}
}
void xiao()
{
	ans[1]=0;
	for(int i=1;i<=n;i++)
	{
		int ma=i;
		for(int j=i+1;j<=n;j++)if(tabs(t[j][i])>tabs(t[ma][i]))ma=i;
		for(int j=1;j<=n+1;j++)swap(t[i][j],t[ma][j]);
		if(!t[i][i])return;
		for(int j=1;j<=n;j++)
		{
			if(j==i)continue;
			double num=t[j][i]/t[i][i];
			for(int k=1;k<=n+1;k++)t[j][k]-=t[i][k]*num;
		}
	}
	for(int i=1;i<=n;i++)ans[i]=t[i][n+1]/t[i][i];
}
signed main()
{
	//freopen("xor.in","r",stdin);
	//freopen("xor.out","w",stdout);
	scanf("%lld%lld",&n,&m);
	for(int i=1;i<=m;i++)
	{
		int u,v,w;
		scanf("%lld%lld%lld",&u,&v,&w);
		a[u].push_back((node){v,w}),r[u]++;
		if(u!=v)a[v].push_back((node){u,w}),r[v]++;
	}
	double res=0;
	for(int i=1;i<=1e9;i<<=1)
	{
		build(i);
		xiao();
		res+=i*ans[1];
	}
	printf("%.3f",res);
	return 0;
}

T4:

考试时代错变量了,导致一测 \(0\)

不考虑共线情况,答案为 \(\binom{3}{(n+1)\times(m+1)}\div 3!\)

考虑共线情况。

首先定住定一个点在 \((0,0)\),枚举第二个点,找到第一二个点所连成的直线,枚举射线上的整点,这样就定位了一个共线的三点。

这三个点可以看成以第一个点为左下角,第三个点为右上角且边平行于坐标轴的矩阵。

这样像这样三个点可以造成的贡献就是 \((N-n+1)\times(M-m+1)\)\(N,M\) 为大矩阵边长,\(n,m\) 为小矩阵边长。若矩阵不为直线,贡献还要乘二,因为一个矩形有两个对角线。

最后累计答案即可。

时间复杂度:\(O(n^2\times\log(n))\)

#include<iostream>
#include<cstdio>
#define int long long
using namespace std;
int n,m;
int gcd(int x,int y)
{
	if(x==0)return y;
	if(y==0)return x;
	return gcd(y,x%y);
}
signed main()
{
	freopen("trii.in","r",stdin);
	freopen("trii.out","w",stdout); 
	scanf("%lld%lld",&n,&m);
	int num=((n+1)*(m+1))*((n+1)*(m+1)-1)*((n+1)*(m+1)-2)/6;
	for(int i=0;i<=n;i++)
	{
		for(int j=0;j<=m;j++)
		{
			if(i==0&&j==0)continue;
			for(int k=2;;k++)
			{
				int flag=1;
				int g=gcd(i,j);
				if(i==0||j==0)flag=0;
				int xt=i/g,yt=j/g;
				int xx=xt*k,yy=yt*k;
				if(xx<=i&&yy<=j)continue;
				if(xx>n||yy>m)break;
				num-=(flag+1)*(n-xx+1)*(m-yy+1);
			}
		}
	}
	printf("%lld",num);
	return 0;
}
posted @ 2023-02-24 13:52  Gmt丶Fu9ture  阅读(28)  评论(0)    收藏  举报