CSP-S 模拟 29

CSP-S 模拟 29

A. 一个赢家 (card)

\(2n\) 张卡,第 \(i\) 张卡上写着数字 \(i\)。有 \(n\) 个人,这 \(n\) 个人轮流从这些卡中均匀随机拿走两张,不放回。现在每个人手上各有两张卡,手上两张卡上写的数字的和最大的人获胜。显然,胜者可能不止一个,求胜者恰好只有一个的概率。

打表题。

首先我们可以不用考虑顺序,只需考虑方案数即可。

显然的想法是枚举 \(i,j \in [1,2n] (i\ne j)\),钦定 \(a[i]+a[j]\) 为唯一的那个最大值,计算这种情况的方案数,最后所有情况的方案数的 \(\sum\) 即为总方案数。容易发现这样所有合法情况均被包含在内。

看一个例子:

\(n!! = 1 \times 3 \times \dots \times n\)(保证 n 为奇数)

答案为 \(2^6 \times (1!!)\)

再看一个

答案为 \(4^4 \times (3!!)\)

再看一个

答案为 \(6^3 \times (5!!)\)

发现答案为 \(x^p \times (y!!)\) 的形式。

又通过手模发现 \(y=(2n-2)-2x-1\)

\(dp[i][j]\) 为钦定 \(a[i]+a[j]\) 为最大值时的方案数。

于是就有 dp 雏形了。

再打个暴力与 dp 中的每一个值相比较,修正 \(2.5h\) 即可得到以下 \(n^2\) 代码:

#include<bits/stdc++.h>
#define int long long

using namespace std;

const int Size=(1<<20)+1;
char buf[Size],*p1=buf,*p2=buf;
char buffer[Size];
int op1=-1;
const int op2=Size-1;
#define getchar()                                                              \
(tt == ss && (tt=(ss=In)+fread(In, 1, 1 << 20, stdin), ss == tt)     \
	? EOF                                                                 \
	: *ss++)
char In[1<<20],*ss=In,*tt=In;
inline int read()
{
	int x=0,c=getchar(),f=0;
	for(;c>'9'||c<'0';f=c=='-',c=getchar());
	for(;c>='0'&&c<='9';c=getchar())
		x=(x<<1)+(x<<3)+(c^48);
	return f?-x:x;
}
inline void write(int x)
{
	if(x<0) x=-x,putchar('-');
	if(x>9)  write(x/10);
	putchar(x%10+'0');
}

const int mod=1e9+7;
int n;
int ksm(int x,int p)
{
    if(x<=0||p<0) return 1;
    int ans=1;
    while(p)
    {
        if(p&1) ans*=x,ans%=mod;
        x*=x;
        x%=mod;
        p>>=1;
    }
    return ans;
}

const int N=5e6+5;
int f[N],frac[N<<1],frac2[N<<1];
int sum[N],sum2[N];
int g[N];
int tot;

void init()
{
    int maxn=5e6;
    f[0]=1;
    int tot=1;
    frac[0]=g[0]=frac[1]=1;
    frac2[0]=frac2[1]=1;
    g[1]=2;
    for(int i=2;i<=n;i++) 
    {
        frac[i]=frac[i-1]*i%mod;
        frac2[i]=frac2[i-2]*i%mod;
        g[i]=g[i-1]*2%mod;
    }     
    for(int i=1;i<=(n>>1);i++) g[i]=ksm(g[i],mod-2);
    for(int i=1;i<=(n>>1);i++) f[i]=frac[i<<1]*g[i]%mod;
}

signed main()
{
    //card.in
    n=read()<<1;
    init();

    if(n==2) { cout<<"1"; return 0; }

    int ans=0;
    for(int r=n;r>(n>>1);r--,cout<<"\n")
    for(int l=r-1;l>n-r+1;l--)
    {
        int nw=ans;
        int mid=(l+r+2)>>1;
        if(mid<=(n>>1)) break;
        ans+=ksm(l-(n-r)-1,n-mid)*frac2[max(0ll,(n-2-(max(0ll,n-mid)<<1))-1)]%mod;
        ans%=mod;
        cout<<"l="<<l<<" r="<<r<<" ksm="<<ksm(l-(n-r)-1,n-mid)<<" frac2="<<frac2[max(0ll,(n-2-(max(0ll,n-mid)<<1))-1)]<<" ans="<<ans<<" dtans="<<ans-nw<<"\n";
    }
    cout<<ans*ksm(frac2[n-1],mod-2)%mod<<"\n";

	return 0;
}

如果你有惊人的注意力,你可以发现:友情链接

而这里是打表。

\(n=5\) 为例。打出 \(i,j,dp[i][j]\)

其中 \(dtans=dp[i][j]=ksm\times frac2\pmod {mod}\ , ans=\sum dtans\pmod {mod}\)

使用 ctrl+F。第一区块选中 \(ksm\)\(frac2\),查看出现次数。

例:ksm=1 frac2=34459425

l=19 r=20 ksm=1 frac2=34459425 ans=34459425 dtans=34459425
l=18 r=20 ksm=1 frac2=34459425 ans=68918850 dtans=34459425
l=17 r=20 ksm=16 frac2=2027025 ans=101351250 dtans=32432400
l=16 r=20 ksm=15 frac2=2027025 ans=131756625 dtans=30405375
l=15 r=20 ksm=196 frac2=135135 ans=158243085 dtans=26486460
l=14 r=20 ksm=169 frac2=135135 ans=181080900 dtans=22837815
l=13 r=20 ksm=1728 frac2=10395 ans=199043460 dtans=17962560
l=12 r=20 ksm=1331 frac2=10395 ans=212879205 dtans=13835745
l=11 r=20 ksm=10000 frac2=945 ans=222329205 dtans=9450000
l=10 r=20 ksm=6561 frac2=945 ans=228529350 dtans=6200145
l=9 r=20 ksm=32768 frac2=105 ans=231969990 dtans=3440640
l=8 r=20 ksm=16807 frac2=105 ans=233734725 dtans=1764735
l=7 r=20 ksm=46656 frac2=15 ans=234434565 dtans=699840
l=6 r=20 ksm=15625 frac2=15 ans=234668940 dtans=234375
l=5 r=20 ksm=16384 frac2=3 ans=234718092 dtans=49152
l=4 r=20 ksm=2187 frac2=3 ans=234724653 dtans=6561
l=3 r=20 ksm=256 frac2=1 ans=234724909 dtans=256
l=2 r=20 ksm=1 frac2=1 ans=234724910 dtans=1

l=18 r=19 ksm=16 frac2=2027025 ans=267157310 dtans=32432400
l=17 r=19 ksm=15 frac2=2027025 ans=297562685 dtans=30405375
l=16 r=19 ksm=196 frac2=135135 ans=324049145 dtans=26486460
l=15 r=19 ksm=169 frac2=135135 ans=346886960 dtans=22837815
l=14 r=19 ksm=1728 frac2=10395 ans=364849520 dtans=17962560
l=13 r=19 ksm=1331 frac2=10395 ans=378685265 dtans=13835745
l=12 r=19 ksm=10000 frac2=945 ans=388135265 dtans=9450000
l=11 r=19 ksm=6561 frac2=945 ans=394335410 dtans=6200145
l=10 r=19 ksm=32768 frac2=105 ans=397776050 dtans=3440640
l=9 r=19 ksm=16807 frac2=105 ans=399540785 dtans=1764735
l=8 r=19 ksm=46656 frac2=15 ans=400240625 dtans=699840
l=7 r=19 ksm=15625 frac2=15 ans=400475000 dtans=234375
l=6 r=19 ksm=16384 frac2=3 ans=400524152 dtans=49152
l=5 r=19 ksm=2187 frac2=3 ans=400530713 dtans=6561
l=4 r=19 ksm=256 frac2=1 ans=400530969 dtans=256
l=3 r=19 ksm=1 frac2=1 ans=400530970 dtans=1

l=17 r=18 ksm=196 frac2=135135 ans=427017430 dtans=26486460
l=16 r=18 ksm=169 frac2=135135 ans=449855245 dtans=22837815
l=15 r=18 ksm=1728 frac2=10395 ans=467817805 dtans=17962560
l=14 r=18 ksm=1331 frac2=10395 ans=481653550 dtans=13835745
l=13 r=18 ksm=10000 frac2=945 ans=491103550 dtans=9450000
l=12 r=18 ksm=6561 frac2=945 ans=497303695 dtans=6200145
l=11 r=18 ksm=32768 frac2=105 ans=500744335 dtans=3440640
l=10 r=18 ksm=16807 frac2=105 ans=502509070 dtans=1764735
l=9 r=18 ksm=46656 frac2=15 ans=503208910 dtans=699840
l=8 r=18 ksm=15625 frac2=15 ans=503443285 dtans=234375
l=7 r=18 ksm=16384 frac2=3 ans=503492437 dtans=49152
l=6 r=18 ksm=2187 frac2=3 ans=503498998 dtans=6561
l=5 r=18 ksm=256 frac2=1 ans=503499254 dtans=256
l=4 r=18 ksm=1 frac2=1 ans=503499255 dtans=1

l=16 r=17 ksm=1728 frac2=10395 ans=521461815 dtans=17962560
l=15 r=17 ksm=1331 frac2=10395 ans=535297560 dtans=13835745
l=14 r=17 ksm=10000 frac2=945 ans=544747560 dtans=9450000
l=13 r=17 ksm=6561 frac2=945 ans=550947705 dtans=6200145
l=12 r=17 ksm=32768 frac2=105 ans=554388345 dtans=3440640
l=11 r=17 ksm=16807 frac2=105 ans=556153080 dtans=1764735
l=10 r=17 ksm=46656 frac2=15 ans=556852920 dtans=699840
l=9 r=17 ksm=15625 frac2=15 ans=557087295 dtans=234375
l=8 r=17 ksm=16384 frac2=3 ans=557136447 dtans=49152
l=7 r=17 ksm=2187 frac2=3 ans=557143008 dtans=6561
l=6 r=17 ksm=256 frac2=1 ans=557143264 dtans=256
l=5 r=17 ksm=1 frac2=1 ans=557143265 dtans=1

l=15 r=16 ksm=10000 frac2=945 ans=566593265 dtans=9450000
l=14 r=16 ksm=6561 frac2=945 ans=572793410 dtans=6200145
l=13 r=16 ksm=32768 frac2=105 ans=576234050 dtans=3440640
l=12 r=16 ksm=16807 frac2=105 ans=577998785 dtans=1764735
l=11 r=16 ksm=46656 frac2=15 ans=578698625 dtans=699840
l=10 r=16 ksm=15625 frac2=15 ans=578933000 dtans=234375
l=9 r=16 ksm=16384 frac2=3 ans=578982152 dtans=49152
l=8 r=16 ksm=2187 frac2=3 ans=578988713 dtans=6561
l=7 r=16 ksm=256 frac2=1 ans=578988969 dtans=256
l=6 r=16 ksm=1 frac2=1 ans=578988970 dtans=1

l=14 r=15 ksm=32768 frac2=105 ans=582429610 dtans=3440640
l=13 r=15 ksm=16807 frac2=105 ans=584194345 dtans=1764735
l=12 r=15 ksm=46656 frac2=15 ans=584894185 dtans=699840
l=11 r=15 ksm=15625 frac2=15 ans=585128560 dtans=234375
l=10 r=15 ksm=16384 frac2=3 ans=585177712 dtans=49152
l=9 r=15 ksm=2187 frac2=3 ans=585184273 dtans=6561
l=8 r=15 ksm=256 frac2=1 ans=585184529 dtans=256
l=7 r=15 ksm=1 frac2=1 ans=585184530 dtans=1

l=13 r=14 ksm=46656 frac2=15 ans=585884370 dtans=699840
l=12 r=14 ksm=15625 frac2=15 ans=586118745 dtans=234375
l=11 r=14 ksm=16384 frac2=3 ans=586167897 dtans=49152
l=10 r=14 ksm=2187 frac2=3 ans=586174458 dtans=6561
l=9 r=14 ksm=256 frac2=1 ans=586174714 dtans=256
l=8 r=14 ksm=1 frac2=1 ans=586174715 dtans=1

l=12 r=13 ksm=16384 frac2=3 ans=586223867 dtans=49152
l=11 r=13 ksm=2187 frac2=3 ans=586230428 dtans=6561
l=10 r=13 ksm=256 frac2=1 ans=586230684 dtans=256
l=9 r=13 ksm=1 frac2=1 ans=586230685 dtans=1

l=11 r=12 ksm=256 frac2=1 ans=586230941 dtans=256
l=10 r=12 ksm=1 frac2=1 ans=586230942 dtans=1


458729432


找到规律了吧!

出现次数从上至下:1 1 2 2 3 3 4 4 ....

所以我们只用枚举第一区块,对答案的贡献次数很容易求出。

这题就做完了。

你赛后发现这是有道理的,相当于固定最大值,找到有几个合法的位置,发现当前答案和位置无关。

概率要乘总方案数的逆元。即 \(\frac{1}{(2n-1)!!}\pmod {mod}\)

Code:

#include<bits/stdc++.h>
#define int long long

using namespace std;

const int Size=(1<<20)+1;
char buf[Size],*p1=buf,*p2=buf;
char buffer[Size];
int op1=-1;
const int op2=Size-1;
#define getchar()                                                              \
(tt == ss && (tt=(ss=In)+fread(In, 1, 1 << 20, stdin), ss == tt)     \
	? EOF                                                                 \
	: *ss++)
char In[1<<20],*ss=In,*tt=In;
inline int read()
{
	int x=0,c=getchar(),f=0;
	for(;c>'9'||c<'0';f=c=='-',c=getchar());
	for(;c>='0'&&c<='9';c=getchar())
		x=(x<<1)+(x<<3)+(c^48);
	return f?-x:x;
}
inline void write(int x)
{
	if(x<0) x=-x,putchar('-');
	if(x>9)  write(x/10);
	putchar(x%10+'0');
}

const int mod=1e9+7;
int n;
int ksm(int x,int p)
{
    if(x<=0||p<0) return 1;
    int ans=1;
    while(p)
    {
        if(p&1) ans*=x,ans%=mod;
        x*=x;
        x%=mod;
        p>>=1;
    }
    return ans;
}

const int N=5e6+5;
int frac2[N];
int tot;

void init()
{
    int maxn=n>>1;
    int tot=1;
    frac2[0]=1;//frac2[1]=1;
    for(int i=1;i<=maxn;i++) frac2[i]=frac2[i-1]*((i<<1)|1)%mod;
}

signed main()
{
	freopen("card.in","r",stdin);
	freopen("card.out","w",stdout);  


    n=read()<<1;
    init();  if(n==2) { cout<<"1"; return 0; }

    int ans=0,nwcnt=1,r=n;
    for(int l=r-1;l>1;l--)
    {
        nwcnt++;
        int nw=ans;
        int mid=(l+r+2)>>1;
        ans+=ksm(l-(n-r)-1,n-mid)*frac2[max(0ll,(n-2-(max(0ll,n-mid)<<1))-1)>>1]%mod*(nwcnt>>1)%mod;
        ans%=mod;
    }
    cout<<ans*ksm(frac2[(n-1)>>1],mod-2)%mod<<"\n";
    
	return 0;
}

(大道至简)

B. 排列计数 (count)

显然不会。有简单做法。

先把每个数所有的平方因子踢出,再把无平方因子的这些数放到数组 \(a\) 中。

发现当且仅当答案排列 \(\exists p[i] = p[i-1]\) 时,该排列不合法。就是答案排列不能有两个相邻的数相等。

设两个相邻的数相等为一个不合法数对。

我们先将 \(a\) 从小到大排序,统计每个数的出现次数,然后去重。

直接设 \(dp[i][j]\) 表示当前选完前 \(i -1\) 个数字(所有 \(< a[i]\) 的数字均被选),有 \(j\) 组不合法数对的方案数。

目标 \(dp[siz+1][0]\)\(siz\) 为去重之后 \(a\) 数组大小。

设去重之前 \(a[i]\) 共有 \(cnt[a[i]]\) 个。

那么转移时我们枚举:

\(j\):选第 \(i\) 个数字之前不合法的数对有 \(j\) 个。

\(k\):选完第 \(i\) 个数字之后不合法的数对有 \(k\) 个。

\(c\):破坏原来的 \(c\) 组不合法数对。即为在这 \(c\) 组数对中间均先插入一个 \(a[i]\)

\(New=k-(j-c)\) 可以直接计算出:需要新加 \(New\) 组不合法数对。

我们拆分我们的转移步骤为:先破坏原先 \(c\) 组不合法数对,再新添加 \(New\) 个不合法数对。

显然如果 \(New < 0\) 直接跳过。(你不可能新加 \(<0\) 个不合法数对。

我们现在考虑求方案数。

\(sum=\sum_{j=1}^{i-1} cnt[a[i]]\)

从转移步骤一步一步求:

首先原来有 \(dp[i][j]\) 种方案。

使用两次插板法:

\(C_{cnt-1}^{cnt-1-New}\) 插出 \(cnt-New\) 个段,这样就会新添 \(New\) 组不合法数对。

\(cnt-New\) 个段的贡献分为两部分:

先在原先 \(j\) 组中随机破坏 \(c\) 组,\(C_j^c\)

剩下 \(cnt-New-c\) 个段插入除原先 \(j\) 组中间其余的地方,一共有 \(sum+1-j\) 个,\(C^{sum+1-j}_{cnt-New-c}\)

\(cnt\)\(a[i]\) 可以任意排列,\(A^{cnt}_{cnt}=cnt!\)

其中若 \(C()\)\(A()\) 不合法,令答案为 \(0\)

相乘即可。设答案为 \(nw\)

那么转移:\(dp[i+1][k]+ nw \to dp[i+1][k]\) 即可。

时间复杂度 \(O(x) << O(n^3)\),实测最大点 \(O(3.3n^2)\)

Code:

#include<bits/stdc++.h>
#define int long long

using namespace std;

const int Size=(1<<20)+1;
char buf[Size],*p1=buf,*p2=buf;
char buffer[Size];
int op1=-1;
const int op2=Size-1;
#define getchar()                                                              \
(tt == ss && (tt=(ss=In)+fread(In, 1, 1 << 20, stdin), ss == tt)     \
	? EOF                                                                 \
	: *ss++)
char In[1<<20],*ss=In,*tt=In;
inline int read()
{
	int x=0,c=getchar(),f=0;
	for(;c>'9'||c<'0';f=c=='-',c=getchar());
	for(;c>='0'&&c<='9';c=getchar())
		x=(x<<1)+(x<<3)+(c^48);
	return f?-x:x;
}
inline void write(int x)
{
	if(x<0) x=-x,putchar('-');
	if(x>9)  write(x/10);
	putchar(x%10+'0');
}

int n;
const int N=1005;
int a[N];
int b[100000],cnt;
int dp[N][N];
const int mod=1e9+7;

int find(int x)
{
    for(int i=1;i<=cnt&&b[i]<=x;i++)
    while(x%b[i]==0) x/=b[i];
    return x;
}

int frac[1005],g[1005];


int ksm(int x,int p)
{
    int ans=1;
    while(p)
    {
        if(p&1) ans*=x,ans%=mod;
        x*=x;
        x%=mod;
        p>>=1;
    }
    return ans;
}

inline int A(int n,int m) 
{ 
    if(n<0||m<0||n-m<0) return 0;
    return frac[n]*g[n-m]%mod;
}
inline int C(int n,int m) 
{ 
    if(n<0||m<0||n-m<0) return 0;
    return frac[n]*g[m]%mod*g[n-m]%mod; 
}
 
void init()
{
    frac[0]=g[0]=1;
    for(int i=1;i<=1000;i++)
    {
        frac[i]=frac[i-1]*i%mod;
        g[i]=ksm(frac[i],mod-2);
        // cout<<frac[i]<<" "<<g[i]<<"\n";
    }
}

int max(int x,int y) { return x>y?x:y; }
int min(int x,int y) { return x<y?x:y; }

void solve()
{
    vector<int> v;
    map<int,int> mp;
    memset(dp,0,sizeof(dp));
    // mp.clear();
    // v.clear();
    n=read();
    for(int i=1;i<=n;i++)
    {
        int x=read();
        a[i]=find(x);
        mp[a[i]]++;
        if(mp[a[i]]==1) v.push_back(a[i]);
    }
    // for(int i:v) cout<<i<<" ";
    // for(int i=1;i<=n;i++) cout<<a[i]<<" ";
    // cout<<"\n";
    // int ans=0;
    sort(v.begin(),v.end());
    sort(a+1,a+1+n);
    
    int sum=0;
    dp[0][0]=1;
    for(int i=0;i<v.size();i++)
    {
        int num=v[i],cnt=mp[v[i]];

        for(int j=0;j<max(1ll,sum);j++) // 初态不合法
        if(dp[i][j])
        {
            for(int k=0;k<j+cnt;k++) // 末态不合法
            for(int c=min(j,cnt);c>=0;c--) // 取消原先不合法
            {
                int last=j-c;//剩的不合法
                int New=k-last;
                if(last>k) {/*cout<<"Failed on #1\n";*/break;}
                // cout<<dp[i][j]<<" "<<A(cnt,cnt)<<" "<<C(cnt-1,cnt-1-New)<<" "<<C(j,c)<<" "<<C(sum+1-j,cnt-New-c)<<"\n";
                dp[i+1][k]+=dp[i][j]*A(cnt,cnt)%mod*C(cnt-1,cnt-1-New)%mod*C(j,c)%mod*C(sum+1-j,cnt-New-c)%mod;
                dp[i+1][k]%=mod;
                // cout<<dp[i+1][k]<<"\n";
            }           
        }
        sum+=cnt; 
    }

    cout<<dp[v.size()][0]<<"\n";
}

signed main()
{

	// count
	// freopen("a.in","r",stdin);
	// freopen("a.out","w",stdout);
	freopen("count.in","r",stdin);
	freopen("count.out","w",stdout);

    init();
    // cout<<frac[2]<<" "<<g[2]<<" "<<g[0]<<" "<<frac[2]*g[2]%mod*g[0]%mod<<"\n";

    for(int i=2;i*i<=1000000000;i++) b[++cnt]=i*i;//,mp[i*i]=1;
    // cout<<cnt<<"\n";

    int T=read();
    while(T--) solve();

	return 0;
}

C. 树上染黑 (black)

树上求期望。咕。

Code:

#include <bits/stdc++.h>
#define int long long
using namespace std;

const int N = 1 << 20;
const int mod = 998244353;
int n, a[N];
int siz[N];
int frac[N], g[N];
vector<int> E[N];

int qpow(int x, int y)
{
    int ans = 1, mi = x;
    while (y)
    {
        if (y & 1)
            ans = ans * mi % mod;
        mi = mi * mi % mod;
        y >>= 1;
    }
    return ans;
}

void dfs1(int x, int f)
{
    siz[x] = 1;
    for (int y : E[x])
    {
        if (f == y)
            continue;
        dfs1(y, x);
        siz[x] += siz[y];
    }
}

void dfs2(int x, int f)
{
    for (int y : E[x])
    {
        if (f == y)
            continue;
        a[y] = (a[x] + g[n - siz[y] + 1] - g[siz[y] + 1] + mod) % mod;
        dfs2(y, x);
    }
}

signed main()
{
    freopen("black.in", "r", stdin);
    freopen("black.out", "w", stdout);
    cin >> n;
    for (int i = 1; i < n; i++)
    {
        int u, v;
        cin >> u >> v;
        E[u].push_back(v);
        E[v].push_back(u);
    }
    dfs1(1, 0);
    frac[0] = 1;
    g[0] = 1;
    g[1] = 1;
    for (int i = 2; i <= N; i++)
        g[i] = (mod - (mod / i) * g[mod % i] % mod) % mod;
    for (int i = 1; i <= N; i++)
        frac[i] = frac[i - 1] * i % mod;
    for (int i = 1; i <= n; i++)
        a[1] = (a[1] + g[siz[i] + 1]) % mod;
    dfs2(1, 0);
    for (int i = 1; i <= n; i++)
    {
        int sum = (n - 1 + g[n + 1] - a[i] + mod) % mod;
        int ans = ((n - 1) * frac[n - 1] + frac[n] * sum % mod + mod) % mod;
        cout << ans << '\n';
    }
    return 0;
}

D. 摆放花盆 (gardener)

Code:

posted @ 2025-10-15 22:11  Wy_x  阅读(10)  评论(0)    收藏  举报