kuangbin数学2 解题报告

10 篇文章 0 订阅
6 篇文章 0 订阅

这部分组合数学挺多的,记录下。

A - Rooks LightOJ - 1005(排列数+去重)

多组样例:n*n的放个,要放k个棋子,棋子之间互不相同,要求不同行不同列,求方案数
放一个棋子,相当于挑走一个行和一个列,所以考虑组合数。又由于棋子不互相同,所以乘上排列数 A k k A^k_k Akk,就是答案。组合数可以用n^2递推。

int t,n,k;
const int maxn=55;
ll c[maxn][maxn];
ll kase;
ll solve(){
    if(k>n) return 0;
    ll res=c[n][k]*c[n][k];
    rep(i,1,k){
        res*=i;
    }
    return res;
}

int main(){
    //freopen("in.txt","r",stdin);
    c[0][0]=1;
    rep(i,1,maxn-1){
        c[i][0]=c[i][i]=1;
        rep(j,1,i-1){
            c[i][j]=c[i-1][j-1]+c[i-1][j];
        }
    }
    read(t);
    while(t--){
        read(n),read(k);
        printf("Case %lld: %lld\n",++kase,solve());
    }
    return 0;
}

B - Parallelogram Counting LightOJ - 1058(几何定理+不用map统计相同元素)

给n个点,问这些点为端点的直线可以组成多少个平行四边形。
用到几何定理:平行四边形对角线互相平分,所以统计每个中点的覆盖次数cnt,则以这个中点组成的平行四边形就是 C n 2 C^2_n Cn2个。但这题用map会mle,因此,我们将点进行排序后,遍历统计相同点有几个。

#define pdd pair<int,int>
const int maxn=1e3+100;
int n;
int t;
struct node{
	int x,y;
	bool operator<(const node&a)const{
		return x==a.x?y<a.y:x<a.x;
	}
	bool operator==(const node&a)const{
		return x==a.x&&y==a.y;
	}
	bool operator!=(const node&a)const{
		return !(x==a.x&&y==a.y);
	}
}da[maxn];
int kase;
vector<node> save;
int solve(){
	rep(i,1,n){
		rep(j,i+1,n){
			save.push_back({da[i].x+da[j].x,da[i].y+da[j].y});
		}
	}
	sort(save.begin(),save.end());
	int ans=0;
	//save.erase(unique(save.begin(),save.end()),save.end());
	//return save.size()>>1;
	for(int i=0;i<save.size();i++){
		int r=save.size()-1;
		for(int j=i+1;j<save.size();j++){
			if(save[i]!=save[j]){
				r=j-1;
				break;
			}
		}
		int num=r-i+1;
		ans+=(num)*(num-1);
		i=r;
	}
	return ans>>1;
}

signed main()
{
	read(t);
	while(t--){
		save.clear();
		read(n);
		rep(i,1,n){
			read(da[i].x),read(da[i].y);
		}
		printf("Case %lld: %lld\n",++kase,solve());
	}
	return 0;
}

C - Combinations LightOJ - 1067(1e6组合数板子)

很基础的题。。唯一需要注意的是n<m的情况需要特判掉,否则可能会出错(以前遇到过??)。

#define int ll
const int maxn=1e6+100;
ll f[maxn];
ll finv[maxn];
int kase;
ll C(ll n,ll m){
	if(m>n) return 0;
	return ((f[n]*ksm(f[m],mod-2))%mod*ksm(f[n-m],mod-2))%mod;
}
signed main()
{
	f[0]=1;
	rep(i,1,maxn-1) {f[i]=(f[i-1]*i)%mod;}
	/*rep(i,1,10){
		cerr<<f[i]<<" ";
	}
	cerr<<endl;*/
	//cerr<<f[maxn-1]<<endl;
	//finv[maxn-1]=ksm(f[maxn-1],mod-2);
	//cerr<<finv[maxn-1]<<endl;
	//per(i,0,maxn-2) finv[i]=(finv[i+1]*(i+1))%mod;
	//rep(i,0,10){
	//	cerr<<finv[i]<<" ";
	//}
	int t;read(t);
	while(t--){
		int n,k;read(n),read(k);
		printf("Case %d: %lld\n",++kase,C(n,k));
	}
	return 0;
}

D - Arrange the Numbers LightOJ - 1095(错排问题+组合数递推)

错排问题+组合数。需要注意下,它只说前m个数有k个在原位置上,没有说后n-m个数的情况,所以,需要枚举后n-m个数有i个不在原位置上,求和即可。

#define int ll
const int maxn=1e3+100;
int t;
int n,m,k;
int d[maxn];
ll C[maxn][maxn];
ll f[maxn];
void init(){
	d[0]=1;
	d[1]=0;
	d[2]=1;
	rep(i,3,maxn-1){
		d[i]=(i-1)*(d[i-1]+d[i-2])%mod;
	}
	f[0]=1;
	rep(i,1,maxn-1){
		f[i]=f[i-1]*i%mod;
	}
	C[0][0]=1;
	rep(i,1,maxn-1){
		C[i][0]=C[i][i]=1;
		rep(j,1,i-1){
			C[i][j]=(C[i-1][j-1]+C[i-1][j])%mod;
		}
	}
}

ll solve(){
	ll res=0;
	rep(i,0,n-m){
		res=(res+(d[i+m-k]*C[n-m][i])%mod)%mod;	
	}
	return res*C[m][k]%mod;
}

signed main()
{
	init();
	read(t);
	int kase=0;
	while(t--){
		read(n),read(m);read(k);
		printf("Case %lld: %lld\n",++kase,solve()%mod);
	}
	return 0;
}

*E - Problem Makes Problem LightOJ - 1102 (隔板法/看作n+k个空位选n个位置)

如上面标题所说的想:我们看作有n个物品,k-1个板子,由于n个物品一定要选完,所以,我们可以这么看:我们要放的东西一共有n+k-1个,其中n个是球,k-1个为板子,所以,我们就能知道答案是 C n + k − 1 k − 1 C^{k-1}_{n+k-1} Cn+k1k1

const int maxn=2e6+100;
ll t,n,k;
ll kase;
ll f[maxn];
void init(){
	f[0]=1;
	rep(i,1,maxn-1){
		f[i]=f[i-1]*i%mod;
	}
}
ll solve(){
	return (((f[n+k-1]*ksm(f[n],mod-2))%mod)*ksm(f[k-1],mod-2))%mod;
}

int main()
{
	read(t);
	init();
	while(t--){
		read(n),read(k);
		printf("Case %lld: %lld\n",++kase,solve()%mod);
	}
	return 0;
}

F - How Many Zeroes? LightOJ - 1140(数位dp)

数位dp,但是好像复杂度不大对劲?不过还是卡过去了就是了Orz

#define int ll
ll t,m,n;
ll kase;
int len,num[20];
ll dp[12][82][3][3];
ll C[45][45];
//int path[45];
ll solve(int pos,int cnt,int zero,int need){
	if(pos==0){
		if(zero) {return 1;}
		else {/*per(i,1,len){cerr<<path[i];}cerr<<endl;*/return cnt;}
	}
	if(~dp[pos][cnt][zero][need])return dp[pos][cnt][zero][need];
	/*if(!need&&!zero){
		ll res=0;
		rep(i,0,pos){
			res+=ksm(9,pos-i)*C[pos][i];
		}
		return dp[pos][cnt][zero][need]=res;
	}*/
	int lim=9;
	if(need) lim=num[pos];
	ll res=0;
	rep(i,0,lim){
		res+=solve(pos-1,cnt+((i==0)&&(!zero)),zero&&(i==0),need&&(i==num[pos]));
	}
	return dp[pos][cnt][zero][need]=res;
}
ll cal(ll x){
	if(x<0)return 0;
	if(x==0) return 1;
	len=0;
	while(x){
		num[++len]=x%10;
		x/=10;
	}
	memset(dp,-1,sizeof(dp));
	ll res=solve(len,0,1,1);
	return res;
}

signed main()
{
	read(t);
	//init();
	while(t--){
		read(m),read(n);
		ll res1=cal(n);
		ll res2=cal(m-1);
		printf("Case %lld: %lld\n",++kase,res1-res2);
	}
	return 0;
}

G - Counting Perfect BST LightOJ - 1170(卡特兰数)

首先,题目中的perfect power是可以通过打表求出1~1e10内所有数的。那么,就可以通过二分,得出题目所给范围有几个数字给我们构成bst。之后,我们发现,n个不相等的数,可以组成的bst的数目为:
f [ n ] = ∑ i = 0 n f [ i ] ∗ f [ n − i − 1 ] f[n]=\sum_{i=0}^nf[i]*f[n-i-1] f[n]=i=0nf[i]f[ni1]这是卡特兰数的定义。根据卡特兰数的递推式: f [ n ] = f [ n − 1 ] ∗ ( 4 n − 2 ) n + 1 f[n]={f[n-1]*(4n-2)\over n+1} f[n]=n+1f[n1](4n2)

#define int ll
ll t,a,b;
const ll LIM=1e10+10;
const int maxn=1e6+100;
vector<ll> table;
ll f[maxn];
ll kase;
void init(){
    ll lim=sqrt(LIM);
    rep(i,2,lim){
        ll j=i*i;
        while(j<LIM){
            table.push_back(j);
            j*=i;
        }
    }
    sort(table.begin(),table.end());
    table.erase(unique(table.begin(),table.end()),table.end());
    f[0]=1;
    rep(i,1,maxn-1){
        f[i]=(((f[i-1]*(4*i-2))%mod)*ksm(i+1,mod-2))%mod;
    }
    f[0]=0;
}

ll solve(){
    int s=lower_bound(table.begin(),table.end(),a)-table.begin();
    int t=upper_bound(table.begin(),table.end(),b)-table.begin();
    ll num=t-s;
    //cerr<<num<<endl;
    //cerr<<t<<" "<<s<<endl;
    return f[num]%mod;
}   

signed main()
{
    init();
    read(t);
    /*for(int i=0;i<10;i++){
        cerr<<table[i]<<" ";
    }
    cerr<<endl;*/
    //cerr<<table.size()<<endl;
    while(t--){
        read(a),read(b);
        printf("Case %lld: %lld\n",++kase,solve());
    }
	return 0;
}

*H - The Vindictive Coach(计数类dp)(巧妙的状态设计)

应该可以想到这个过程是个dp。暴力的想法是用状压存下那些人已经在队内,哪些人还没入队,但是这样需要1<<50的空间,无法实现。考虑简化状态,我们其实只关心在剩下的人中有多少人比现在队尾的人高,多少人比队尾的人矮,所以,我们设计状态:dp[i][j][k],现在剩下i人,j其中j人比队尾矮,现在需要找比队尾高(1)/矮(1)的人。注意开ull来取模。

int t,n,m;
const int maxn=50+10;
ull dp[maxn][maxn][3];
bool vis[maxn][maxn][3];
/*struct node{
	int l,k,c;
	node(int L,int K,int C){
		l=L;
		k=K;
		c=C;
	}
};*/
//vector<node>save;
ull search(int left,int k,int con){
	//剩下left人,其中k人比他矮,现在需要找比他高(1)/矮(0)的
	if(!left) {
		/*cerr<<"==================================================="<<endl;
		for(auto x:save){
			cerr<<x.l<<" "<<x.k<<" "<<x.c<<endl;
		}
		cerr<<"==================================================="<<endl;*/
		return 1;
	}
//	save.push_back(node(left,k,con));
	if(vis[left][k][con])return dp[left][k][con];
	vis[left][k][con]=1;
	if(con){
		ull res=0;
		rep(i,k,left-1){
			res+=search(left-1,i,con^1);
		}
//		save.pop_back();
		return dp[left][k][con]=res;
	}
	else{
		ull res=0;
		rep(i,0,k-1){
			res+=search(left-1,i,con^1);
		}
		//save.pop_back();
		return dp[left][k][con]=res;
	}
}
ull solve(){
	memset(vis,0,sizeof(vis));
	if(m==1){
		if(n<=3)return 1;
		else return search(n-3,0,1);
	}
	else{
		return search(n-1,m-1,0);
	}
}

signed main(){
	read(t);
	rep(kase,1,t){
		read(n),read(m);
		printf("Case "),write(kase),printf(": "),write(solve()),putchar('\n');
	}
	return 0;
}

*I - One Unit Machine (插空)

又是一道组合数学。有n种元素,每种元素ki个,要将这些元素排成一列,要求是,第i+1种元素全部用完前,第i种元素必须先用完。
考虑第1种元素,只有一种排列方式。第二种元素,因为它结束的要比第一种完,所以,直接拿出一个放到最后,剩下 k 2 − 1 k_2-1 k21个元素和前面的 k 1 k_1 k1个元素插空组合,则,一共有 k 1 + k 2 − 1 k_1+k_2-1 k1+k21个空位,需要挑出 k 2 − 1 k_2-1 k21个给第二种元素,以此类推。

#define int ll
const int maxn=1e3+100;
int t,n,a[maxn];
ll f[maxn*maxn];
ll finv[maxn*maxn];
int kase;
ll ksm(ll a,ll n){
    ll res=1;
    while(n){
        if(n&1)res=res*a%mod;
        a=a*a%mod;
        n>>=1;
    }
    return res%mod;
}

ll inv(ll x){
    return ksm(x,mod-2);
}
void init(){
    f[0]=1;
    rep(i,1,maxn*maxn-1){
        f[i]=f[i-1]*i%mod;
    }
    finv[maxn*maxn-1]=inv(f[maxn*maxn-1]);
    per(i,0,maxn*maxn-2){
        finv[i]=finv[i+1]*(i+1)%mod;
    }
}

ll C(ll n,ll m){
    //cerr<<f[n]<<" "<<finv[n-m]<<" "<<finv[m]<<endl;
    return ((f[n]*finv[n-m])%mod*finv[m])%mod;
}

int solve(){
    int sum=0;
    ll res=1;
    rep(i,1,n){
        sum+=a[i];
        res=res*C(sum-1,a[i]-1)%mod;
    }
    return res;
}

signed main(){
    read(t);
    init();
    while(t--){
        read(n);
        rep(i,1,n) read(a[i]);
        printf("Case %lld: %lld\n",++kase,solve());
    }
    return 0;
}

*J - Colorful Board(找规律、枚举+dp+组合计数)

1 0 1 0 1 0
0 1 0 1 0 1
1 0 1 0 1 0
0 1 0 1 0 1
1 0 1 0 1 0
0 1 0 1 0 1

通过暴力我们可以看出:和(0,0)成偶数距离的点分布很有特点,并且占了整个矩阵的大概一半。同时,我们可以证明,和(0,0)成偶数距离的位置之间也互相成偶数距离(偶数-偶数仍为偶数),因此,我们可以把整个矩阵看作两部分:与(0,0)成偶数距离的和成奇数距离的,这两部分不能有相同的颜色,把这两部分分别看作两个线性的部分处理就行了(因为内部之间没有影响,可以相同可以不同)。
问题转化为:现在有n个格子,k种颜色,求只用其中的j(1<=j<=k)种种颜色有多少种方案的多组询问问题。(为什么看作多组询问?因为再处理两部分组合的时候需要用到其中一部分用多少种颜色。)这是个dp问题,看代码注释。

//#define int ll
int t,n,m,k;
int kase;
int mod=1000000007;
const int maxn=25;
const int lim=210;
const int maxk=55;
int dp[lim][maxk];
int C[maxk][maxk];
inline void init(){
	dp[0][0]=1;
	rep(i,1,lim-1){
		rep(j,1,maxk-1){
			dp[i][j]=(1ll*dp[i-1][j-1]*j%mod+1ll*dp[i-1][j]*j%mod)%mod;
			//i个格子,用j种颜色的方案数
			//从j转移过来:j种颜色中挑一种。
			//从j-1转移过来:原来的j-1是从j种挑j-1种,所以现在是它的C(j,j-1)倍
		}
	}
	C[0][0]=1;
	rep(i,1,maxk-1){
		C[i][0]=C[i][i]=1;
		rep(j,1,i-1){
			C[i][j]=(C[i-1][j]+C[i-1][j-1])%mod;
		}
	}
}

inline int solve(){
	if(n==0&&m==0){
		return k;
	}
	//cerr<<C[10][5]<<endl;
	int num1,num2;
	num1=((n+1)*(m+1)>>1);
	num2=(n+1)*(m+1)-num1;
	int res=0;
	rep(i,1,k){
		rep(j,1,k-i){
			res=(res+(1ll*C[k][i]*dp[num1][i]%mod)*(1ll*dp[num2][j]*C[k-i][j]%mod)%mod)%mod;
		}
	}
	return res%mod;
}

signed main(){
	read(t);
	init();
	while(t--){
		read(n),read(m),read(k);
		//cerr<<n<<" "<<m<<" "<<k<<endl;
		printf("Case %d: %d\n",++kase,solve());
	}
	return 0;
}

K - Unlucky Bird

简单的来说,就是两辆想向的匀减速的火车和一只匀速的鸟,给出初速度、加速度,他们会在距离为0时停下,求他们之间的距离和鸟在此期间的移动距离。高中物理公式就行了。

#include <bits/stdc++.h>
using namespace std;
#define pdd pair<double,double>
int t;
double v1,v2,v3,a1,a2;
int kase;
pdd solve(){
	double fi=v1*v1/(2*a1)+v2*v2/(2*a2);
	double se=max(v2/a2,v1/a1)*v3;
	return make_pair(fi,se);
}

int main(){
	//freopen("in.txt","r",stdin);
	scanf("%d",&t);
	while(t--){
		scanf("%lf %lf %lf %lf %lf",&v1,&v2,&v3,&a1,&a2);
		pdd res=solve();
		printf("Case %d: %.6lf %.6lf\n",++kase,res.first,res.second);
	}
	return 0;
}

* L - Strange Game(n!中q的次数的log求法,组合数对非质数取模,排列组合)

思路比较明确,就是求出又几组可行的字符串对,然后看到哪个人就没得选了就谁输。可以看出组合数目就是 1 2 k l ( k − 1 ) m C l m {1\over2}k^l(k-1)^{m}C_l^m 21kl(k1)mClm,之后对其mod n后+1就是loser的序号了。
而难点在于 C l m C_l^m Clm的求值,我们需要对他进行分解质因数,对于 C n m C_n^m Cnm需要求出n!,(n-m)!,m!的分解质因数,然后用快速幂重新组合,这个过程中再来取模,否则可能会导致mod之后直接为0的结果。
有一个小的知识点:对于n的阶乘,要求其中q被乘的次数,可以这样算:

int cnt=0;
while(n){
	cnt+=n/q;
	n/=q;
}
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define re register
#define rep(i,a,b) for(re int (i)=(a);(i)<=(b);++i)
#define per(i,a,b) for(re int (i)=(b);(i)>=(a);--i)
template<typename T>
void read(T&x){
	x=0;
	ll f=1;
	char ch=getchar();
	while(!isdigit(ch)){
		if(ch=='-')f*=-1;
		ch=getchar();
	}
	while(isdigit(ch)){
		x=x*10+ch-'0';
		ch=getchar();
	}
	x*=f;
}
//===============================================================
#define int ll
const int maxn=1e5+100;
ll t,n,k,l,m;
int save=0;
int pri[maxn],tot;
bool vis[maxn];
ll ksm(ll a,ll n,ll p){
    ll res=1;
    while(n){
        if(n&1) res=res*a%p;
        a=a*a%p;
        n>>=1;
    }
    return res;
}
void init(){
    for(int i=2;i<maxn;i++){
        if(!vis[i])pri[tot++]=i;
        for(int j=0;j<tot&&i*pri[j]<maxn;j++){
            vis[i*pri[j]]=1;
            if(i%pri[j]==0)break;
        }
    }
}
int cnt[maxn];
void upd(ll x,ll fac){
    for(int i=0;i<tot;i++){
        int t=x;
        while(t){
            cnt[i]+=fac*(t/pri[i]);
            t/=pri[i];
        }
    }
}

ll inv(ll x,ll p){
    return ksm(x,p-2,p)%p;
}
ll C(ll n,ll m,int p){
    if(m==0) return 1;
    memset(cnt,0,sizeof(cnt));
    upd(n,1),upd(n-m,-1),upd(m,-1);
    ll res=1;
    rep(i,0,tot-1){
        res=res*ksm(pri[i],cnt[i],p)%p;
    }
    return res;
}

ll solve(){
    if(m==0){
        return ksm(k,l,n)%n+1;
    }
    //cerr<<C(l,m,n)<<" "<<ksm(k*(k-1)%n,m,n)<<" "<<ksm(k,l-m,n)<<endl;
    //return (((((C(l,m,n)*ksm(k*(k-1),m,n)%n)*ksm(k,l-m,n)%n)*ksm(2,n-2,n)%n)%n+1));
    int lima=l,limb=m;
    if(k%2==0) lima--;
    else limb--;
    ll p1=C(l,m,n);
    ll p2=ksm(k,lima,n);
    ll p3=ksm(k-1,limb,n);
    //if(p3%2==0) p3/=2;
    //else p2/=2;
    return (((p1*p2)%n)*p3)*(k/2)%n+1;
}
ll kase;
signed main(){
    read(t);
    init();
    while(t--){
        read(n),read(k),read(l),read(m);
        printf("Case %lld: %lld\n",++kase,solve());
    }
    return 0;
}

*M - Worst Case Trie(找规律、循环节)

可以找规律。对于一个trie,第一层根节点一个,第二层节点数是其所有字符数k,第三层由于不能有和上一层重复的字符,所以是k-1,……,所以,节点数为ans=1+k+k*(k-1)+k*(k-1)*……*1,可以发现ans=1+k*((k-1)*+(k-1)*……*1),即: a n s k = a n s k − 1 ∗ k + 1 ans_k=ans_{k-1}*k+1 ansk=ansk1k+1,然后,因为只要后4位,当k=10000时, a n s 10000 = a 0 = 1 ans_{10000}=a_{0}=1 ans10000=a0=1,重新开始,所以打出1~10000的表,k mod10000输出就行了。
注意需要特判下ans>=10000之后的情况,他要的后四位需要含有前导零,所以记得补0。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define re register
#define rep(i, a, b) for (re int(i) = (a); (i) <= (b); ++i)
#define per(i, a, b) for (re int(i) = (b); (i) >= (a); --i)
template <typename T>
void read(T &x)
{
    x = 0;
    ll f = 1;
    char ch = getchar();
    while (!isdigit(ch))
    {
        if (ch == '-')
            f *= -1;
        ch = getchar();
    }
    while (isdigit(ch))
    {
        x = x * 10 + ch - '0';
        ch = getchar();
    }
    x *= f;
}
//===============================================================
#define int ll
const int maxn = 1e4 + 100;
int f[maxn];
int t, k;
int kase;
int Div = -1;
int solve()
{
    return f[k % 10000];
}

signed main()
{
    f[0] = 1;
    rep(i, 1, maxn - 1)
    {
        f[i] = (f[i - 1] * i + 1) % 10000;
        if (f[i - 1] * i >= 10000 && Div == -1)
            Div = i;
    }
    read(t);
    while (t--)
    {
        read(k);
        if (k < Div)
            printf("Case %lld: %lld\n", ++kase, solve());
        else
            printf("Case %lld: %04lld\n", ++kase, solve());
    }
    return 0;
}

O - Grid Coloring(思维+组合计数)

有点考思维的一题。重点在于抓住它的限制为上一行和下一行颜色不能相同。若我们考虑一行一行地计算,会需要记录下上一行的情况,并且本行的计数也较为麻烦,因此,考虑转化思维,一列一列地计数,这样,因为他们处在同一列,我们就只用记上一个不能涂的位置,然后快速幂+记忆化优化下就行了。

#include <bits/stdc++.h>
using namespace std;
struct node{
    int x,y;
    node(int X,int Y){
        x=X;
        y=Y;
    }
};
const int mod=1e9;
typedef long long ll;
int ksm(int a,int n){
    int res=1;
    while(n){
        if(n&1)res=1ll*res*a%mod;
        a=1ll*a*a%mod;
        n>>=1;
    }
    return res%mod;
}
int t,n,m,k,b;
int kase;
vector<node>da;
const int maxn=1e6+100;
int dp[maxn];
int cal(int x){
    if(~dp[x])return dp[x];
    //cerr<<x<<endl;
    if(x==0)return 1;
    if(x==1)return k%mod;
    return dp[x]=1ll*k*ksm(k-1,x-1)%mod;
}
int solve(){
    sort(da.begin(),da.end(),[](const node&a,const node&b){
        return a.y==b.y?a.x<b.x:a.y<b.y;
    });
    int p=0;
    int res=1;
    for(int i=1;i<=n;i++){
        int pre=0;
        while(p<da.size()&&da[p].y<=i){
            res=1ll*res*cal(da[p].x-pre-1)%mod;
            pre=da[p].x;
            p++;
        }
        res=1ll*res*cal(m+1-pre-1)%mod;
    }
    return res;
}

int main(){
    //freopen("in.txt","r",stdin);
    scanf("%d",&t);
    while(t--){
        memset(dp,-1,sizeof(dp));
        da.clear();
        scanf("%d%d%d%d",&m,&n,&k,&b);
        for(int i=0;i<b;i++){
            int x,y;
            scanf("%d %d",&x,&y);
            da.push_back(node(x,y));
        }
        printf("Case %d: %d\n",++kase,solve()%mod);
    }
    return 0;
}

P - Strange Summation(数位dp)

这题没大想清楚Orz
先放着有空再想。

#include <bits/stdc++.h>
using namespace std;
#define rep(i,a,b) for(int (i)=(a);(i)<=(b);++i)
#define per(i,a,b) for(int (i)=(b);(i)>=(a);--i)
typedef long long ll;
template<typename T>
void read(T &x){
    x=0;
    ll f=1;
    char ch=getchar();
    while(!isdigit(ch)){
        if(ch=='-')f*=-1;
        ch=getchar();
    }
    while(isdigit(ch)){
        x=x*10+ch-'0';
        ch=getchar();
    }
    x*=f;
}
//===============================
#define int ll
int t;
ll p,q;
int kase;
const int maxn=115;
int num[maxn],len;
int cnt[maxn];
void init(){
    int base[maxn];
    base[0]=1;
    rep(i,1,63){
        base[i]=base[i-1]*2;
    }
}

int dfs(int pos,int need,int zero){
    if(pos>len) {return 0;}
    int lim=1;
    if(need) lim=num[pos];
    int res=0;
    int s=0;
    if(zero) s=1;
    rep(i,s,lim){
        res+=dfs(pos+1,need&&i==num[pos],zero&&i==0);
    }
    if(!(need&&num[pos]==0))cnt[pos]+=res+1;
    if(pos!=len) cnt[pos]++;
    return res+1;
}

void solve(ll x,int fac){
    //len=0;
    //memset(cnt,0,sizeof(cnt));
    //while(x){
    //    num[++len]=(x&1);
    //    x>>=1;
    //}
    //reverse(num+1,num+1+len);
    /*rep(i,1,len){
        cerr<<num[i]<<" ";
    }
    cerr<<endl;*/
    //dfs(1,1,1);
    /*rep(i,1,len){
        cerr<<cnt[i]<<" ";
    }
    cerr<<endl;*/
    //per(i,1,len){
    //    cnt[i-1]+=cnt[i]/2;
    //    cnt[i]=(cnt[i]&1);
    //}
    /*rep(i,1,len){
        cerr<<cnt[i]<<" ";
    }
    cerr<<endl;*/
    //ll res=0;
    //rep(i,1,len){
    //    res<<=1;
    //    res|=cnt[i];
    //}
    //return res;
    cnt[0]+=x*fac;
    rep(i,1,63-1){
        ll m=1ll<<i,len=1;
        if(m>x) break;
        while(m<=x/2){
            cnt[i]+=m/2*fac;
            m<<=1ll,len<<=1ll;
        }
        m=x-m+1;
        cnt[i]+=(m/len/2*len)*fac;
        if((m/len)&1) cnt[i]+=(m%len)*fac;
    }
}

signed main(){
    //freopen("in.txt","r",stdin);
    read(t);
    while(t--){
        memset(cnt,0,sizeof(cnt));
        read(p),read(q);
        solve(q,1),solve(p-1,-1);
        //cerr<<p1<<" "<<p2<<endl;
        ll res=0;
        int len=0;
        while(q){
            len++;
            q>>=1;
        }
        rep(i,0,len-1){
            if(cnt[i]&1){
                res|=(1ll<<(len-i-1));
            }
        }
        printf("Case %d: %lld\n",++kase,res);
    }
    return 0;
}

*Q - ICPC Guards(DP)

国内博客几乎找不到题解,但在github上看到份代码+题解:
https://github.com/DrSwad/CompetitiveProgramming/commit/d8553916606d1bf1de637c0d93ce15448d87f032
貌似是把点看作顶点,在二分图上跑dp?学点图论再来看吧Orz

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define re register
#define rep(i,a,b) for(re int (i)=(a);(i)<=(b);++i)
#define per(i,a,b) for(re int (i)=(b);(i)>=(a);--i)
ll mod=1000000007;
template<typename T>
void read(T&x){
	x=0;
	ll f=1;
	char ch=getchar();
	while(!isdigit(ch)){
		if(ch=='-')f*=-1;
		ch=getchar();
	}
	while(isdigit(ch)){
		x=x*10+ch-'0';
		ch=getchar();
	}
	x*=f;
}
//====================================
#define int ll
inline ll ksm(ll a,ll n){
	ll res=1;
	while(n){
		if(n&1) res=res*a%mod;
		a=a*a%mod;
		n>>=1;
	}
	return res;
}
const int maxn=1e5+100;
const int maxk=50+10;
ll t,n,k;
ll f[maxn];
ll finv[maxn];
ll dp[maxn][maxk];
ll kase;
inline void init(){
	f[0]=1;
	rep(i,1,maxn-1){
		f[i]=f[i-1]*i%mod;
	}
	finv[maxn-1]=ksm(f[maxn-1],mod-2);
	per(i,0,maxn-2){
		finv[i]=finv[i+1]*(i+1)%mod;
	}
	/*rep(i,1,10){
		cerr<<f[i]<<" ";
	}
	cerr<<endl;
	rep(i,1,10){
		cerr<<finv[i]<<" ";
	}
	cerr<<endl;*/
	ll inv2=ksm(2,mod-2);
	dp[0][0]=1;
	rep(j,1,50){
		ll sum=0;
		rep(i,2,maxn-1){
			//sum=(sum+dp[i-1][j-1]*finv[j-1]%mod*finv[j-1]%mod)%mod;
			//dp[i][j]=sum*f[i]%mod*f[i-1]%mod;
			sum=(sum+dp[i-2][j-1]*finv[i-2]%mod*finv[i-2]%mod)%mod;
			dp[i][j]=sum*f[i]%mod*f[i-1]%mod*inv2%mod;
		}
	}
	/*rep(i,1,5){
		rep(j,1,5){
			cerr<<dp[i][j]<<" ";
		}
		cerr<<endl;
	}*/
}

ll solve(){
	return dp[n][k];
}

signed main(){
	//freopen("in.txt","r",stdin);
	//freopen("out.txt","w",stdout);
	init();
	read(t);
	while(t--){
		read(n),read(k);
		printf("Case %lld: %lld\n",++kase,solve());
	}
	return 0;
}

*R - Pair of Touching Circles(思维、枚举)

这题挺有意思的。思路是枚举两个圆之间的相对x距离和y距离,检查这个距离是否为整数,为整数的话,就再枚举其中一个圆的半径,同时得出另一个圆的半径。之后,算出这两个圆围成的矩形的大小,得出整个HxW矩阵可以容纳多少个这样的图形就行了。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define rep(i,a,b) for(int (i)=(a);(i)<=(b);++i)
#define per(i,a,b) for(int (i)=(b);(i)>=(a);--i)
template<typename T>
void read(T&x){
	x=0;
	char ch=getchar();
	ll f=1;
	while(!isdigit(ch)){
		if(ch=='-')f*=-1;
		ch=getchar();
	}
	while(isdigit(ch)){
		x=x*10+ch-'0';
		ch=getchar();
	}
	x*=f;
}
//====================================
int h,w,t;
int kase;
/*ll solve(){
	ll res=0;
	int lim=max(w,h);
	int numw=w+1,numh=h+1;
	rep(i,1,lim){
		int limm=(lim-2*i)/2;
		rep(j,1,limm){
			if(2*i+2*j<=w&&max(2*i,2*j)<=h){
				res+=(w-2*i-2*j+1)*min((h-i-i+1),(h-j-j+1));
				//cerr<<i<<" ";
			}
			if(2*i+2*j<=h&&max(2*i,2*j)<=w){
				res+=(h-2*i-2*j+1)*min((w-i-i+1),(w-j-j+1));
				//cerr<<numh-i-j<<" "<<min((numw-i-i),(numh-i-i));
				//cerr<<"*"<<i<<" ";
				//cerr<<(h-2*i-2*j+1)<<endl;
			}
		}
	}
	return res;
}*/
#define int ll
ll solve(){
	ll res=0;
	int cnt=0;
	rep(i,0,w/2){
		rep(j,0,h/2){
		//枚举两个圆心的相对距离
			if(i==0&&j==0)continue;//圆心重合特判掉
			int t=sqrt(i*i+j*j);
			if(t*t!=(i*i+j*j)) continue;//检查是否为整数
			rep(r1,1,t-1){
				//枚举其中一个半径
				int r2=t-r1;
				int l=min(-r1,i-r2),r=max(r1,i+r2);
				int t=max(j+r2,r1),b=min(-r1,j-r2);
				//算出边界
				int dy=r-l,dx=t-b;
				//算所占举行大小
				if(dy>w||dx>h) continue;
				ll fac=1;
				if(i&&j) fac*=2;
				//若圆心连线不和坐标轴平行,则两个圆的位置交换为不同的情况,需要*2
				res+=1ll*(w-dy+1)*(h-dx+1)*fac;//计数
			}
		}
	}
	return res;
}
#undef int
int main(){
	//freopen("in.txt","r",stdin);
	read(t);
	while(t--){
		read(h),read(w);
		printf("Case %d: %lld\n",++kase,solve());
	}
	return 0;
}

*S - Energetic Pandas(思维,计数)

看着好像要枚举,但其实可以直接计数。我们观察下会发现,cap比较大的熊猫选择的范围为比较小的的子集,所以,一个cap大的熊猫能选的,小的一定能选。那么,对于一个cap小的熊猫,若能选的竹子是s个,而前面cap比他大的熊猫有x只,则,这x只熊猫选的竹子一定是从s的一部分中选的,那么,现在轮到这个小cap熊猫选的时候,就还剩下s-x个竹子能选。
注意下选不了的情况。

#include<bits/stdc++.h>
using namespace std;
#define rep(i,a,b) for(int (i)=(a);(i)<=(b);++i)
#define per(i,a,b) for(int (i)=(b);(i)>=(a);--i)
#define gc() getchar()
typedef long long ll;
template<typename T>
void read(T&x){
	x=0;
	ll f=1;
	char c=getchar();
	while(!isdigit(c)){
		if(c=='-')f*=-1;
		c=getchar();
	}
	while(isdigit(c)){
		x=x*10+c-'0';
		c=gc();
	}
	x*=f;
}
template<typename T>
void write(T x){
	if(x<0) putchar('-'),x=-x;
	if(x>9) write(x/10);
	putchar(x%10+'0');
}
//==========================================
const int maxn=1e3+100;
int n,t;
ll kase;
ll mod=1000000007;
ll w[maxn];
ll cap[maxn];
ll solve(){
	sort(w+1,w+1+n);
	sort(cap+1,cap+1+n);
	int p=1;
	ll res=1;
	rep(i,1,n){
		while(p<=n&&w[p]<=cap[i])++p;
		res=(res*max(0,(p-i)))%mod;
	}
	return res;
}

int main(){
	//freopen("in.txt","r",stdin);
	read(t);
	while(t--){
		read(n);
		rep(i,1,n) read(w[i]);
		rep(i,1,n) read(cap[i]);
		printf("Case %lld: %lld\n",++kase,solve());
	}
	return 0;
}

*T - The Queue(树形dp+组合计数)

一开始想到拓扑排序,但好像并不好处理?还是选择了树形dp。
大问题是整个树的排队顺序,而分解出来一个个小问题就是每个子树中的排列顺序,然后各个兄弟之间自由组合。因此,我们可以得到状态转移方程:
d p [ u ] = ( d p [ u ] ∗ d p [ v ] ) ∗ C [ s i z [ u ] + s i z [ v ] − 1 ] [ s i z [ v ] ] dp[u]=(dp[u]*dp[v])*C[siz[u]+siz[v]-1][siz[v]] dp[u]=(dp[u]dp[v])C[siz[u]+siz[v]1][siz[v]]
后面的组合数原理类似于隔板法。前面dp,根据乘法原理是相乘的。
注意树根不一定为1。

#include <bits/stdc++.h>
using namespace std;
#define rep(i,a,b) for(int i=a;i<=b;++i)
#define per(i,a,b) for(int i=b;i>=a;--i)
typedef long long ll;
const int mod=1000000007;
template<typename T>
void read(T&x){
    x=0;
    char ch=getchar();
    ll f=1;
    while(!isdigit(ch)){
        if(ch=='-')f*=-1;
        ch=getchar();
    }
    while(isdigit(ch)){
        x=x*10+ch-'0';
        ch=getchar();
    }
    x*=f;
}
//======================================================
#define int ll
int t,n;
const int maxn=1e3+100;
struct Edge{
    int to,next;
}e[maxn<<2];
int head[maxn],cnt;
void add(int x,int y){
    e[cnt].to=y;
    e[cnt].next=head[x];
    head[x]=cnt++;
}
int kase;
int in[maxn];//
int dp[maxn];//
int siz[maxn];//
int c[maxn][maxn];
void init(){
    c[0][0]=1;
    rep(i,1,maxn-1){
        c[i][0]=c[i][i]=1;
        rep(j,1,i-1){
            c[i][j]=(c[i-1][j]+c[i-1][j-1])%mod;
        }
    }
}
void dfs(int u,int fa){
    dp[u]=1;
    siz[u]=1;
    for(int i=head[u];~i;i=e[i].next){
        int v=e[i].to;
        if(v==fa) continue;
        dfs(v,u);
        dp[u]=(dp[u]*dp[v]%mod)*c[siz[u]+siz[v]-1][siz[v]]%mod;
        siz[u]+=siz[v];
    }
}
int solve(){
    int rt=-1;
    rep(i,1,n){
        if(!in[i]) {rt=i;break;}
    }
    dfs(rt,-1);
    /*rep(i,1,n){
        cerr<<dp[i]<<" ";
    }
    cerr<<endl;
    cerr<<rt<<endl;
    */
    return dp[rt];
}

signed main(){
    read(t);
    init();
    while(t--){
        memset(head,-1,sizeof(head));
        read(n);
        cnt=0;
        memset(in,0,sizeof(in));
        memset(dp,0,sizeof(dp));
        memset(siz,0,sizeof(siz));
        rep(i,2,n){
            int x,y;
            read(x),read(y);
            add(x,y);
            in[y]++;
        }
        printf("Case %d: %d\n",++kase,solve());
    }
    return 0;
}

*U - Necklace(poyla 定理)

这个以后开个贴单独讲吧Orz(挖坑挖坑)

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define rep(i,a,b) for(int (i)=(a);(i)<=(b);++i)
#define per(i,a,b) for(int (i)=(b);(i)>=(a);--i)
template<typename T>
void read(T&x){
	x=0;
	ll f=1;
	char ch=getchar();
	while(!isdigit(ch)){
		if(ch=='-')f*=-1;
		ch=getchar();
	}
	while(isdigit(ch)){
		x=x*10+ch-'0';
		ch=getchar();
	}
	x*=f;
}
//=========================================
#define int ll
ll mod=1e9+7;
ll t,n,k;
ll kase;
ll ksm(ll a,ll n){
	ll res=1;
	while(n){
		if(n&1) res=res*a%mod;
		a=a*a%mod;
		n>>=1;
	}
	return res;
}
ll euler(ll x){
	ll lim=sqrt(x);
	ll res=x;
	rep(i,2,lim){
		if(x%i==0){
			res=res-res/i;
			while(x%i==0){
				x/=i;
			}
		}
	}
	if(x>1){
		res=res-res/x;
	}
	return res;
}
ll solve(){
	ll lim=sqrt(n);
	ll res=0;
	rep(i,1,lim){
		if(n%i==0){
			res=(res+euler(n/i)*ksm(k,i)%mod)%mod;
			if(i*i!=n){
				res=(res+euler(i)*ksm(k,n/i)%mod)%mod;
			}
		}
	}
	return res*ksm(n,mod-2)%mod;
}

signed main(){
	//freopen("in.txt","r",stdin);
	//freopen("out.txt","w",stdout);
	read(t);
	while(t--){
		read(n),read(k);
		printf("Case %lld: %lld\n",++kase,solve());
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值