题目集地址 The 2021 ICPC Asia Jinan Regional Contest
我们只做了K题签到题。。
思维:Optimal Strategy,Insidemen,Permutation Pair
数学:Arithmetic Sequence,Determinant,Strange Series
动态规划:Game Coin
搜索:Search For Mafuyu
2-SAT:Coloring Rectangles
C Optimal Strategy 组合数
题目大意:有 n 件物品,第 i 件的价值为 a[i]。A 和 B 轮流取物品,A 先手。每个玩家都要最大化自己取到的物品的价值和,求有多少种可能的游戏过程。
思路:参考2021icpc济南题解
在偶数情况下,只要有一个人选了当前最大的数,那么后面那个人就一定要跟着选一个一样大的 。不然一定会亏。
比如说:1 1 2 2 3 3,如果先手选了3,后手一定会选3。
所以在排列中最大的一定是成对在一起的。
当我们把排列中最大的数删掉之后第二大的就一定会成对,删掉第二大的之后第三大的就一定会成对…
所以我们可以从最小的开始考虑,先把最小的放好,再把第二小的成对插进去,这就是挡板法了,不难发现这一步等价于球不同,盒子不同,有空盒的球盒模型。依次推到下去我们可以得到数字全部都是偶数的推导公式。
假设cnt是当前已经放置的球的个数,当前要放置的第i个球,两两成对共有t=(c[i]/2)(向下取整)对。对应于球放盒的问题中,可以放置的位置就是盒子,有cnt+1个盒子,盒子不同,有t个球,球是相同的,盒子可以为空,利用挡板法,可知对应的组合数的公式为
C
t
+
c
n
t
c
n
t
C_{t+cnt}^{cnt}
Ct+cntcnt,因为球与球之间是不同的,所以再乘一个球的全排列,
A
c
[
i
]
c
[
i
]
A_{c[i]}^{c[i]}
Ac[i]c[i]
存在奇数个时就会存在单独的数字,我拿了之后我必然比你多一个。这样可以得出结论当存在单独的数的时候优先拿走最大单个数。
放在排列中,单个的10必然是第一个因为它比所有的数字都大,而同样的单个的8一定会在1和4前面。
这体现了一个什么问题?单个对排列个数没卵用,因为我们是把大的往小的插空,单个的放在最前面就好了(但是记得乘排列的时候不能省略这一位)。
最后得到的公式就是这样的
∏
i
=
1
n
c
[
i
]
!
C
∑
j
=
1
i
−
1
c
[
j
]
+
⌊
c
[
i
]
/
2
⌋
⌊
c
[
i
]
/
2
⌋
\prod_{i=1}^nc[i]!C_{\sum_{j=1}^{i-1}c[j]+\lfloor{c[i]/2}\rfloor}^{\lfloor{c[i]/2}\rfloor}
i=1∏nc[i]!C∑j=1i−1c[j]+⌊c[i]/2⌋⌊c[i]/2⌋
AC代码:
#include <bits/stdc++.h>
#define ll long long
#define INF 0x3f3f3f3f
using namespace std;
const int mod=998244353;
const int N=1e6+6;
int c[N];
ll pow(ll a, ll n, ll p) //快速幂 a^n % p
{
ll ans = 1;
while(n)
{
if(n & 1) ans = ans * a % p;
a = a * a % p;
n >>= 1;
}
return ans;
}
ll niyuan(ll a, ll p) //费马小定理求逆元
{
return pow(a, p - 2, p);
}
ll A(int n,int m)//A_n^m
{
ll res = 1;
for(int i = n;i>=n-m+1;i--)
{
res=(res*i)%mod;
}
return res;
}
ll C(int n,int m)//C_n^m
{
if(n==0||m==0)
{
return 1;
}
return A(n,m)*niyuan(A(m,m),mod)%mod;
}
void solve()
{
int n;
scanf("%d",&n);
int cnt=0;
for(int i = 1;i <= n;i++)
{
int t;
scanf("%d",&t);
c[t]++;
}
ll ans=1;
for(int i = 1;i <= n;i++)
{
int t = c[i]/2;
if(t){
ans = (ans*C(cnt+t,t)%mod*A(c[i],c[i]))%mod;
}
cnt+=c[i];
}
printf("%lld\n",ans);
}
int main()
{
int t = 1;
while(t--)
{
solve();
}
return 0;
}
H Game Coin
题目大意:Bob第i天需要a[i]个游戏金币来满足自己的游戏购物欲望。获取金币有两种方法,第一种方法是每个金币可以直接花费t元购买,第二种方法是购买游戏商城内部的金币卡来每天领取临时金币,总共有n种金币卡,第j种金币卡有一个购买价格c[j],使用期限d[j]和每天的金币领取数量w[j]。临时金币只能当天使用,第二天就失效。
倘若第i天花费c[j]元购买了第j种金币卡,那么从第i 天到第i+ d[j]-1天每天都可以用该金币卡来领取w[i]个临时金币。每种金币卡可以无限数量购买,但是每次购买新的卡会把手上已有的卡给覆盖掉,保证手上最多只有一张卡。(例如,第c 天买了一张金币卡i,可以用来领取w[i]个临时金币,然后购买一张金币卡j(这时会覆盖掉金币卡i),可以继续用新卡来领取w[j]个金币,通过这种方式第太天可以获得w[i]+ w[j]个金币
计算满足Bob m天的游戏购物欲望最少需要花多少钱。m ≤100000, n≤400,
∑
a
[
i
]
≤
500000
\sum a[i]≤500000
∑a[i]≤500000
c
[
j
]
,
d
[
j
]
,
w
[
j
]
∈
[
0
,
109
]
c[j], d[j], w[j] \in [0,109]
c[j],d[j],w[j]∈[0,109]
思路:
K Search For Mafuyu 欧拉序列
题目大意:有n个房间,(n-1)对是连通的,M等概率隐藏在S的各个非1号房间中.现在K从1号房间出发,要找到M,K可以在连通的房间中移动,移动一步耗时一秒。对于每组样例,输出最小的期望时间.
思路:就是欧拉序列,首先有一个规律就是结果与你遍历的顺序是无关的,我们按照欧拉序遍历所有的点,记录每一个点第一次到达的时间,求和在除以n-1就可以了。
#include <bits/stdc++.h>
#define ll long long
#define INF 0x3f3f3f3f
using namespace std;
struct edge{
int to;
int next;
}E[202];
int head[101],cnt,cntt;
int f1[101];
void add(int from, int to)
{
cnt++;
E[cnt].to = to;
E[cnt].next = head[from];
head[from] = cnt;
}
void dfs(int u,int fa)
{
for(int i = head[u];i;i = E[i].next)
{
int v = E[i].to;
if(v==fa)continue;
cntt++;
f1[v]=cntt;
dfs(v,u);
cntt++;
}
}
void solve()
{
int n;
scanf("%d",&n);
for(int i = 1;i <= n-1;i++){
int from,to;
scanf("%d%d",&from,&to);
add(from,to);
add(to,from);
}
dfs(1,1);
ll res = 0;
for(int i = 1;i <= n;i++)
{
res+=f1[i];
}
double ans = res*1.0/(n-1);
printf("%.10lf\n",ans);
}
int main()
{
// freopen("in.txt","r",stdin);
int t;
scanf("%d",&t);
while(t--)
{
solve();
cnt=0;cntt=0;
memset(head,0,sizeof(head));
memset(E,0,sizeof(E));
}
return 0;
}