NOIP模拟13「工业题·卡常题·玄学题」

T1:工业题

基本思路

  这题有一个重要的小转化:

我们将原来的函数看作一个矩阵,\(f(i,j-1)*a\)相当于从\(j-1\)向右走一步并贡献a,\(f(i-1,j)*b\)相当于从\(i-1\)向下走一步并贡献b

  那么问题就转化成了求从第\(0\)行与第\(0\)列的所有点走到点\((m,n)\)的所有方案数的总贡献
  在一个点,对于他之前的点的所有走法,他都有可能向下或右走并带来贡献,所以是统计所有方案数。
  易知从点\((i,j)\)到点\((m,n)\)的走的步数是\(m-i+n-j)\),又由于在每一个点都有向下或向右走的可能,我们可以只关注向下(或向右),看每一步向下(或向右)走发生在哪个点,那么方案数就是\(C_{n+m-i}^{n-i}\)(从第0列出发)及\(C_{n+m-i}^{m-i}\)(从第0行出发)
  你这么打了,然后你WA了。。。。。。
  为什么?
  注意:这里的C考虑的是决策点,但是,第0行(列)的点一定会向下(右)走,是不会有决策的,因而正确的式子是:

\[C_{n+m-i-1}^{n-i}andC_{n+m-i-1}^{m-i} \]

  上代码:

#include<bits/stdc++.h>
using namespace std;
namespace STD
{
	#define ll long long
	#define rr register 
	const int SIZE=3e5+4;
	const int mod=998244353;
	int n,m;
	ll a,b,ans;
	ll jc[SIZE<<1],inv[SIZE<<1];
	ll fn[SIZE],fm[SIZE];
	ll b_,a_;
	ll qpow(ll base,ll exp)
	{
		rr ll ret=1ll;
		while(exp)
		{
			if(exp&1) ret=ret*base%mod;
			base=base*base%mod;
			exp>>=1ll;
		}
		return ret;
	}
	inline ll C(ll x,ll y){return jc[x]*inv[y]%mod*inv[x-y]%mod;}
	ll read1()
	{
		rr ll x_read=0ll,y_read=1ll;
		rr char c_read=getchar();
		while(c_read<'0'||c_read>'9')
		{
			if(c_read=='-') y_read=-1;
			c_read=getchar();
		}
		while(c_read<='9'&&c_read>='0')
		{
			x_read=x_read*10+(c_read^48);
			c_read=getchar();
		}
		return x_read*y_read;
	}
    int read2() 
	{
		rr int x_read=0ll,y_read=1ll;
		rr char c_read=getchar();
		while(c_read<'0'||c_read>'9')
		{
			if(c_read=='-') y_read=-1;
			c_read=getchar();
		}
		while(c_read<='9'&&c_read>='0')
		{
			x_read=x_read*10+(c_read^48);
			c_read=getchar();
		}
		return x_read*y_read;
	}
};
using namespace STD;
int main()
{
	n=read2(),m=read2(),a=read1(),b=read1();
	a%=mod,b%=mod;
	a_=qpow(a,m);
	b_=qpow(b,n);
	//由于后面a^m与b^n会大量用到,所以预处理
	for(rr int i=1;i<=n;i++){fn[i]=read1();fn[i]%=mod;}
	for(rr int i=1;i<=m;i++){fm[i]=read1();fm[i]%=mod;}
	jc[0]=inv[0]=1ll;
	for(rr int i=1;i<=n+m;i++){jc[i]=1ll*i*jc[i-1]%mod;inv[i]=qpow(jc[i],mod-2);}
	for(rr int i=1;i<=n;i++)
		ans=(ans+fn[i]*C(n+m-i-1,m-1)%mod*qpow(b,n-i)%mod*a_%mod)%mod;
	for(rr int i=1;i<=m;i++)
		ans=(ans+fm[i]*C(n+m-i-1,n-1)%mod*b_%mod*qpow(a,m-i)%mod)%mod;
	int x=printf("%lld",ans);
}

T2:

基本思路:

  考场上想的普通DP,不对。
  实际上是树形DP
  实际上可以将Y方点看作边,然后将X方点看作点,\(n\)个点\(n\)条边,很明显,去掉一条边后就是树,我们的任务就是求能将所有的边都覆盖的最小代价,一条边被覆盖是指它的两个端点至少有一个被选。
  我们可以判出环来然后删去环里的任一条边,然后以删掉的边的端点跑DP即可
  很明显树形DP,DP式就不写在这了,自己推吧,推不出来就自己上代码里找吧。
  状态数组\(c[i][j](j=0,1)\)表示i被选(j=1)或不被选(j=0)是以他为根的子树的最小代价。
  注意被删掉的边也要被覆盖,所以要在两个端点被选的情况里取min
  代码:

#include<bits/stdc++.h>
using namespace std;
namespace STD
{
	#define ll long long
	#define rr register 
	#define pp pair<int,int>
	#define fi first
	#define se second
	#define min(x,y) (x<y?x:y)
	#define inf LONG_LONG_MAX
	#define IT set<int>::iterator 
	const int SIZE=1e6+4;
	int n,root;
	ll ans=inf,a,b;
	set<int> to[SIZE];
	ll val[SIZE];
	ll c[SIZE][2];
	bool vis[SIZE];
	pp edge;
	void dfs(int x,int f)
	{
		static bool ret=0;
		vis[x]=1;
		for(IT it=to[x].begin();it!=to[x].end();it++)
		{
			if(*it==f) continue;
			if(vis[*it]){edge.fi=x,edge.se=*it;ret=1;return;}
			dfs(*it,x);
			if(ret) return;
		}
	}
	void DP(int now,int f)
	{
		//儿子与父亲必须至少有一个选
		for(IT it=to[now].begin();it!=to[now].end();it++)
		{
			if(*it==f) continue;
			DP(*it,now);
			c[now][1]+=min(c[*it][0],c[*it][1]);
			c[now][0]+=c[*it][1];
			//鄙视你的智商,连我都推出来的式子你还推不出来过来看。。。。
		}
		c[now][1]+=val[now];
	}
	int read()
	{
		rr int x_read=0,y_read=1;
		rr char c_read=getchar();
		while(c_read<'0'||c_read>'9')
		{
			if(c_read=='-') y_read=-1;
			c_read=getchar();
		}
		while(c_read<='9'&&c_read>='0')
		{
			x_read=(x_read*10)+(c_read^48);
			c_read=getchar();
		}
		return x_read*y_read;
	}
};
using namespace STD;
int main()
{
	n=read(),a=read(),b=read();
	for(rr int i=1;i<=n;i++)
	{
		int x1=read(),x2=read();
		to[x1].insert(x2),to[x2].insert(x1);
		val[x1]+=a,val[x2]+=b;
	}
	dfs(1,1);
	to[edge.fi].erase(edge.se);
	to[edge.se].erase(edge.fi);
	root=edge.fi;
	DP(root,root);
	ans=min(c[root][1],ans);
	for(rr int i=1;i<=n;i++) c[i][0]=c[i][1]=0;
	root=edge.se;
	DP(root,root);
	ans=min(ans,c[root][1]);
	printf("%lld",ans);
}

T3:玄学题

基本思路

  当我看到-1的时候就已经想到要判指数的奇偶了,但是不会高效地判。
  其实想来也很简单(事后诸葛亮)\(d\)为偶数的情况不决定奇偶,只有奇数的情况才决定,而约数数为奇数只有完全平方数一种情况。
  证明:

对于每个数,他本身以及1都是他的约数,这就有两个约数了。
假如一个数能被某个数整除,这个数及得数都是其约数,那么,除了某个数平方是这个数,那么他的约数一定成对存在。
而假如有个数的平方是这个数,那么它的商还是他自己,那么他这一“对”就只有他自己。
这个平方数约数个数为奇数。

  假如有\(i=p*x^2\)
  要想使\(d(i*j)\)是奇数那么j必须满足\(j=p*y^2\),那么我们只要统计\([1,m]\)内有几个j满足条件即可。
  首先我们可以写一个线性筛法求出每个i的p,并记录。
  线性筛法:

    p[1]=1;
    for(rr int i=2;i<=en;i++)
        p[i*i]=1;
    for(rr int i=2;i<=n;i++)
        if(!p[i])
        {
            p[i]=i;
            for(rr int j=2;j<=en;j++)
            {
                if(j*j*i>n) break;
                p[j*j*i]=i;
            }
        }

  然后,就有j的个数

\[num= \]

\[\sqrt{\frac{m}{p}} \]

  证明:

平方在[1,m]范围内的数最多有\(\sqrt{m}\)个那么平方后\(p\)倍还在范围内的就有\(\sqrt{\frac{m}{p}}\)

  那么我们的算法就是\(O(n)\)的了。
  代码:

#include<bits/stdc++.h>
using namespace std;
namespace STD
{
    #define ll long long
    #define rr register 
    const int SIZE=1e7+4;
    int n,ans;
    ll m;
    int p[SIZE];
    ll read()
    {
        rr ll x_read=0,y_read=1;
        rr char c_read=getchar();
        while(c_read<'0'||c_read>'9')
        {
            if(c_read=='-') y_read='-';
            c_read=getchar();
        }
        while(c_read<='9'&&c_read>='0')
        {
            x_read=(x_read*10)+(c_read^48);
            c_read=getchar();
        }
        return x_read*y_read;
    }
};
using namespace STD;
int main()
{
    n=read(),m=read();
    int en=sqrt(n);
    p[1]=1;
    for(rr int i=2;i<=en;i++)
        p[i*i]=1;
    for(rr int i=2;i<=n;i++)
        if(!p[i])
        {
            p[i]=i;
            for(rr int j=2;j<=en;j++)
            {
                if(j*j*i>n) break;
                p[j*j*i]=i;
            }
        }
    for(rr int i=1;i<=n;i++)
    {
        ll x=sqrt(m/(ll)p[i]);
        if(x&1ll) ans--;
        else ans++;
    }
    printf("%d",ans);
}
posted @ 2021-07-13 17:29  Geek_kay  阅读(46)  评论(0编辑  收藏  举报