这部分组合数学挺多的,记录下。
目录
- A - Rooks LightOJ - 1005(排列数+去重)
- B - Parallelogram Counting LightOJ - 1058(几何定理+不用map统计相同元素)
- C - Combinations LightOJ - 1067(1e6组合数板子)
- D - Arrange the Numbers LightOJ - 1095(错排问题+组合数递推)
- *E - Problem Makes Problem LightOJ - 1102 (隔板法/看作n+k个空位选n个位置)
- F - How Many Zeroes? LightOJ - 1140(数位dp)
- G - Counting Perfect BST LightOJ - 1170(卡特兰数)
- *H - The Vindictive Coach(计数类dp)(巧妙的状态设计)
- *I - One Unit Machine (插空)
- *J - Colorful Board(找规律、枚举+dp+组合计数)
- K - Unlucky Bird
- * L - Strange Game(n!中q的次数的log求法,组合数对非质数取模,排列组合)
- *M - Worst Case Trie(找规律、循环节)
- O - Grid Coloring(思维+组合计数)
- P - Strange Summation(数位dp)
- *Q - ICPC Guards(DP)
- *R - Pair of Touching Circles(思维、枚举)
- *S - Energetic Pandas(思维,计数)
- *T - The Queue(树形dp+组合计数)
- *U - Necklace(poyla 定理)
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+k−1k−1。
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=0∑nf[i]∗f[n−i−1]这是卡特兰数的定义。根据卡特兰数的递推式:
f
[
n
]
=
f
[
n
−
1
]
∗
(
4
n
−
2
)
n
+
1
f[n]={f[n-1]*(4n-2)\over n+1}
f[n]=n+1f[n−1]∗(4n−2)
#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
k2−1个元素和前面的
k
1
k_1
k1个元素插空组合,则,一共有
k
1
+
k
2
−
1
k_1+k_2-1
k1+k2−1个空位,需要挑出
k
2
−
1
k_2-1
k2−1个给第二种元素,以此类推。
#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(k−1)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=ansk−1∗k+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;
}