这两天除了打比赛就是在看组合数学的资料博客。感觉组合数学的题目还是对于我来说挺难的。
先整理部分比赛题,还没完全看懂全部题目。比较菜QAQ
然后整理到今天看的一道启发比较大的组合数学题。(组合问题,容斥原理,高斯消元)
埃森哲杯第十六届上海大学程序设计联赛春季赛暨上海高校金马五校赛
F题:1 + 2 = 3?
题目:小Y在研究数字的时候,发现了一个神奇的等式方程,他屈指算了一下有很多正整数x满足这个等式,比如1和2,现在问题来了,他想知道从小到大第N个满足这个等式的正整数,请你用程序帮他计算一下。
推规律可以发现就是求第i个二进制数1和1之间不相邻的数,可以推出其中关于斐波那契数列的规律。
其实这个题既可以找规律也可以数位dp,关于数位dp的我还得补。
下面是一位大佬的代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
#define INF 0x3f3f3f3f
#define mod 2000000000
LL mi(int n)//快速幂
{
LL a = 1;
LL b = 2;
while(n)
{
if(n % 2)
a *= b;
b *= b;
n >>= 1;
}
return a;
}
int main()
{
int i, j, k;
LL m, n;
LL a[65];
a[0] = 1;
a[1] = 2;
a[2] = 1;
a[3] = 2;
for(i = 4; i <= 64; i++)
{
a[i] = a[i - 1] + a[i - 2];
}
for(i = 2; i <= 64; i++)
{
a[i] += a[i - 1];
}
int T;
scanf("%d", &T);
while(T--)
{
LL ans = 0;
cin >> n;
n++;
while(n)
{
if(n == 1)
{
break;
}
if(n == 2)
{
ans++;
break;
}
for(i = 1; i < 64; i++)//相当于逐位的求
{
if(n <= a[i])
{
n -= a[i - 1];
ans += mi(i - 1);
break;
}
}
}
cout << ans << endl;
}
return 0;
}
D—数字游戏
小埃和小森在玩一个数字游戏,小埃先从区间[L1, R1]里选择1个数字n1,小森看到小埃选的数字后,从[L2,R2]里选择1个数字n2, 将n1和n2连接在一起(n1在前, n2在后),形成一个新的数字,若这个数字可以被mod整除,那么小森获胜,否则小埃获胜。若两个人均采取最优策略,试问谁获胜?
官方题解是
首先 n1 只要考虑%mod 有哪些余数就可以了,这个复杂度是 O(mod)。 而对于 n2,你需要分长度考虑有哪些余数,这个复杂度是 O(2*mod),对于存在同长度一段 大于 mod 的情况下,直接输出 LOSE 即可(这个剪枝比较重要)。 然后就可以枚举 n1 的余数,枚举 n2 的长度来判断是否可能 WIN。O(9*mod)
int main()
{
int t,a,b,c,d,e;
scanf("%d",&t);
while(t--)
{
scanf("%d%d%d%d%d",&a,&b,&c,&d,&e);
if(d-c+1>=e)
printf("LOSE\n");
else
printf("WIN\n");
}
return 0;
}
HDU—5852 Intersection is not allowed!
下面来自:https://blog.csdn.net/v5zsq/article/details/52419187
Description
一个n*n棋盘,第一行第a1,a2,…,ak列有k个棋子,每个棋子都只能往下和往右走,每个棋子的终点是第n行第b1,b2,…,bk列,问这k个棋子到达各种的终点且路径不交叉的方法数
Input
第一行一整数T表示用例组数,每组用例首先输入两整数n和k表示棋盘规模和棋子数量,之后k个整数ai表示k个棋子的起点,最后k个整数bi表示k个棋子的终点(1<=n<=10^5,1<=k<=100,1<=a1 < a2 < … < ak <=n,1<=b1 < b2 < … < bk <=n)
Output
对于每组用例,输出合法方案数,结果模1e9+7
Sample Input
1
5 2
1 2
3 4
Sample Output
50
Solution
首先考虑两个棋子的情况,即一个棋子从a1到b1,另一个棋子从a2到b2,两条路径不交叉的方案数,首先不考虑交叉方案数显然是C(b1-a1+n-1,n-1)*C(b2-a2+n-1,n-1),对于一个a1->b1,a2->b2且路径交叉的方案,如果我们把最下面一个交叉点之后的两条路径交换那么就对应了一个a1->b2,a2->b1的方案;对于一个a1->b2,a2->b1的方案,显然这两条路径必然会相交,那么我们把最后一个交叉点之后的两条路径交换就又对应一个a1->b1,a2->b2的路径交叉的方案,故我们建立了a1->b1,a2->b2交叉路径与a1->b2,a2->b1的方案的一一对应,那么不合法方案数就是C(b2-a1+n-1,n-1)*C(b1-a2+n-1,n-1)
对于多个棋子的情况,由容斥原理,假设某些棋子路径发生了交叉,那么我们采用两个棋子的分析方法,把这些交叉的路径从最后下面一个交叉点之后交换,那么就变成了一个1~n序列的重排,我们不妨设其为c序列,表示第i个棋子从(1,ai)出发到(n,ci),那么这个排列对答案的贡献就取决于c序列的逆序对数,逆序对数为奇则做负贡献,为偶则做正贡献,那么就有
故问题转化为求一个n阶方阵行列式,用高斯消元O(n^3)即可解决
代码来自另一位大佬:https://blog.csdn.net/y1196645376/article/details/52226296
#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
#define eps 1e-6
typedef long long ll;
const ll mod = 1e9+7;
const int N = 2e5+10;
ll inv[N] = {1,1}; //inv[i] i的逆元。
ll fac[N] = {1,1}; // fac[i] i!%mod
ll facv[N] = {1,1}; // facv[i] i!的逆元
void init()
{
for(int i = 2 ; i < N ; i ++)
inv[i] = (mod-mod/i) * inv[mod%i] % mod;
for(int i = 2 ; i < N ; i ++)
facv[i] = facv[i-1] * inv[i] % mod;
for(int i = 2 ; i < N ; i ++)
fac[i] = fac[i-1]*i % mod;
}
ll C(ll n,ll m)
{
if(m < 0) return 0;
return fac[n]*facv[m]%mod*facv[n-m]%mod;
}
int s[110],e[110];
ll a[110][110];
ll det(int n)
{
ll ans=1;
int sign = 0;
for(int i=0;i<n;i++)//当前行
{
for(int j=i+1;j<n;j++)//当前之后的每一行,因为每一行的当前第一个数要转化成0(想想线性代数中行列式的计算)
{
int x=i,y=j;
while(a[y][i])//利用gcd的方法,不停地进行辗转相除
{
ll t=a[x][i]/a[y][i];
for(int k=i;k<n;k++)
a[x][k]=(a[x][k]-a[y][k]*t)%mod;
swap(x,y);
}
if(x!=i)//奇数次交换,则D=-D'整行交换
{
for(int k=0;k<n;k++)
swap(a[i][k],a[x][k]);
sign^=1;
}
}
if(a[i][i]==0)//斜对角中有一个0,则结果为0
{
return 0;
}
else
ans=ans*a[i][i]%mod;
}
if(sign!=0)
ans*=-1;
if(ans<0)
ans+=mod;
return ans;
}
int main()
{
int n,k,t;
init();
scanf("%d",&t);
while(t--)
{
scanf("%d%d",&n,&k);
for(int i = 0 ; i < k ; i ++)
scanf("%d",&s[i]);
for(int i = 0 ; i < k ; i ++)
scanf("%d",&e[i]);
for(int i = 0 ; i < k ; i ++)
{
for(int j = 0 ; j < k ; j ++)
{
a[i][j] = C(n-1+e[j]-s[i],e[j]-s[i]);
}
}
printf("%lld\n",det(k));
}
return 0;
}
这道题利用行列式的性质计算容斥原理的方法我还得回去好好研究研究,还得回去翻翻高代课本了。