1. 程式人生 > 其它 >7.13早考試總結(NOIP模擬13)[工業題·卡常題·玄學題]

7.13早考試總結(NOIP模擬13)[工業題·卡常題·玄學題]

人的記憶本來就是曖昧的,不值得信任。

前言

又是令人頭疼的數學部分。。還是太菜了。。

晚上還有一場,當場裂開。

T1 工業題

解題思路

首先,這個題的暴力還是非常好像的,直接按照題目要求碼就好了。

對於正解,主要思路是計算每一個 \(f(i,0)\)\(f(0,i)\) 對於答案的貢獻。

其實就像從某個點(假設是 \((i,j)\) )向 \((n,m)\) 走,並且僅能向右向上走。

顯然,貢獻就是 \(a^{m-j}\times b^{n-i}\times f(i,j)\times 方案數\)

接下來的問題就是求方案數了,那麼一共有 \(n+m-i-j\) 步,它們無論如何排列的結果都是一樣的。

由於向上走一步其實是相同的操作,但是我們按照他們不同來算了,因此需要除去一些的排列,就是:

\[\dfrac{A_{n+m-i-j}^{n+m-i-j}}{A_{n-i}^{n-i}\times A_{m-j}^{m-j}} \]

算出來就是:

\[\dfrac{(n+m-i-j)!}{(n-i)!\times (m-j)!} \]

階乘以及 a,b 的冪次方都是可以預處理出來的,至於除法直接逆元就好了。

注意,這裡有個魔鬼細節:

在 0 行 0 列之間是無法轉移下去的,但是 1 行 1 列是可以的,並且差距僅僅是行列數之間的差距,因此只需要一些 +1 和 -1 就好了。

另外,注意一下取 \(\bmod\)

code

#include<bits/stdc++.h>
#define int long long
using namespace std;
inline int read()
{
	int x=0,f=1;
	char ch=getchar();
	while(ch>'9'||ch<'0')
	{
		if(ch=='-')	f=-1;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9')
	{
		x=(x<<1)+(x<<3)+(ch^48);
		ch=getchar();
	}
	return x*f;
}
const int N=3e5+10,mod=998244353;
int n,m,a,b,ans,jc[N<<1],s1[N],s2[N],af[N],bf[N];
void Jc_Init()
{
	jc[0]=af[0]=bf[0]=1;
	for(int i=1;i<=m+n;i++)
		jc[i]=jc[i-1]*i%mod;
	for(int i=1;i<=m;i++)
		af[i]=af[i-1]*a%mod;
	for(int i=1;i<=n;i++)
		bf[i]=bf[i-1]*b%mod;
}
int ksm(int x,int y)
{
	int sum=1;
	while(y)
	{
		if(y&1)	sum=sum*x%mod;
		y>>=1;
		x=x*x%mod;
	}
	return sum;
}
signed main()
{
	n=read();
	m=read();
	a=read()%mod;
	b=read()%mod;
	for(int i=1;i<=n;i++)
		s1[i]=read()%mod;
	for(int i=1;i<=m;i++)
		s2[i]=read()%mod;
	Jc_Init();
	for(int i=1;i<=n;i++)
		ans=(ans+jc[n+m-i-1]*ksm(jc[m-1],mod-2)%mod*ksm(jc[n-i],mod-2)%mod*bf[n-i]%mod*af[m]%mod*s1[i]%mod)%mod;
	for(int i=1;i<=m;i++)
		ans=(ans+jc[n+m-i-1]*ksm(jc[m-i],mod-2)%mod*ksm(jc[n-1],mod-2)%mod*af[m-i]%mod*bf[n]%mod*s2[i]%mod)%mod;
	printf("%lld",ans);
	return 0;
}

T2 卡常題

解題思路

不難發現因為每一個 Y 方點都只有兩條邊,因此可以將 Y 方點當作邊, X 方點當作點。

我們就得到了一個有 n 條邊, n 個點的環套樹,顯然,它比一般的樹多了一條邊,怎麼辦呢???

直接拆掉就好了,就是直接拆掉。

又因為每一個 X 方點是可以間隔存在的,剩下的就類似於 沒有上司的舞會 這道題了。

在兩個斷點處進行兩邊樹形 DP 取最小值就好了。

樹形 DP 時分別記錄一下選與不選這個點的值就好了。

code

#include<bits/stdc++.h>
#define int long long
#define f() cout<<"Fuck"<<endl;
using namespace std;
inline int read()
{
	int x=0,f=1;
	char ch=getchar();
	while(ch>'9'||ch<'0')
	{
		if(ch=='-')	f=-1;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9')
	{
		x=(x<<1)+(x<<3)+(ch^48);
		ch=getchar();
	}
	return x*f;
}
const int N=1e6+10;
int tot,head[N],ver[N<<1],nxt[N<<1];
int n,vh,vb,bk1,bk2,ans,h[N],b[N],val[N],f[N][2];
bool vis[N];
void add_edge(int x,int y)
{
	ver[++tot]=y;
	nxt[tot]=head[x];
	head[x]=tot;
}
void dfs(int x,int fro)
{
	if(vis[x])
	{
		bk1=x;
		bk2=fro;
		return ;
	}
	if(bk1||bk2)	return ;
	vis[x]=true;
	for(int i=head[x];i;i=nxt[i])
		if(ver[i]!=fro)
			dfs(ver[i],x);
}
void solve(int x,int fro)
{
	f[x][1]=val[x];
	f[x][0]=0;
	for(int i=head[x];i;i=nxt[i])
		if(ver[i]!=fro&&!(ver[i]==bk1&&x==bk2)&&!(ver[i]==bk2&&x==bk1))
		{
			int to=ver[i];
			solve(to,x);
			f[x][1]+=min(f[to][0],f[to][1]);
			f[x][0]+=f[to][1];
		}
}
signed main()
{
	n=read();
	vh=read();
	vb=read();
	for(int i=1;i<=n;i++)
	{
		h[i]=read();
		b[i]=read();
		val[h[i]]+=vh;
		val[b[i]]+=vb;
		add_edge(h[i],b[i]);
		add_edge(b[i],h[i]);
	}
	dfs(1,0);
	solve(bk1,bk2);
	ans=f[bk1][1];
	solve(bk2,bk1);
	ans=min(ans,f[bk2][1]);
	printf("%lld",ans);
	return 0;
}

T3 玄學題

解題思路

-1 的次冪是由 \(d(i\times j)\) 的個數決定的。

-1 的偶數次冪是 1 對於答案是沒有影響的,因此我們只用考慮 \(d(i\times j)\) 為奇數的時候。

當且僅當 \(i\times j\) 是完全平方數的時候 \(d(i\times j)\) 的值才是奇數。

原因比較顯然:約數就是二個數的積等於這個數;對於非平方數的數每一約數都有一個對應的約數。

接下來把 i 拆成 \(p\times q^2\)( p 沒有平方因子)對於 j 一定有 \(p\times r^2\) 的形式。

因此,對於每一個符合條件的 j 就有 r 種取值也就是 \(\sqrt{\dfrac{m}{p}}\)

顯然的,我們可以通過篩出所有的完全平方數進而求出 p 的值(求的過程類似於尤拉篩篩素數)。

code

#include<bits/stdc++.h>
#define int long long
#define f() cout<<"Fuck"<<endl;
using namespace std;
inline int read()
{
	int x=0,f=1;
	char ch=getchar();
	while(ch>'9'||ch<'0')
	{
		if(ch=='-')	f=-1;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9')
	{
		x=(x<<1)+(x<<3)+(ch^48);
		ch=getchar();
	}
	return x*f;
}
const int N=1e7+10;
int n,m,ans,cnt,f[N],pri[N];
bool vis[N];
signed main()
{
	n=read();
	m=read();
	vis[1]=true;
	pri[++cnt]=1;
	for(int i=2;i<=n;i++)
	{
		if(vis[i]) pri[++cnt]=i;
		int temp=i*i;
		for(int j=1;j<=cnt&&pri[j]*temp<=n;j++)
			vis[pri[j]*temp]=true;
	}
	for(int i=1;i<=n;i++)
	{
		if(!vis[i])	continue;
		for(int j=1;j*i<=n;j++)
			if(!vis[j])
				f[j*i]=j;
	}
	for(int i=1;i<=n;i++)
	{
		if(!f[i])	f[i]=1;
		int temp=sqrt(m/f[i]);
		if(temp&1)	ans--;
		else ans++;
	}
	printf("%lld",ans);
	return 0;
}