2017 ACM-ICPC Asia Xi’an Regional Contest
自闭线段树专场,啥都不会签到罚坐5h,慢慢补题吧。学长讨论的东西一个都听不懂淦
知识点概况
题目 | 知识点 |
---|---|
B - Lovers | 带删除的二分 |
F - God of Gamblers | 找规律,概率 |
J - LOL | 组合计数+暴力+剪枝/状压DP |
H - Arrangement for Contests | 贪心+线段树 |
A - XOR | 线性基+线段树 |
G - Sum of xor sum | DP+异或子集和+位运算 |
题目
B - Lovers
样例
Sample Input
1
3 4
1 2 3
1 2 3
Sample Output
3
题意
对于数组a,b如果a[i]+b[j]>=k,那么配对成功,问最多可以配对多少
思路
a和b排序后,枚举a,找第一个满足条件的条件的j
经典带删除操作的二分,复杂度应该为
O
(
N
l
o
g
N
)
O(NlogN)
O(NlogN),应该可以双指针或者multiset实现,由于不怎么用过多重集,所以用vector实现一波
代码
#include<bits/stdc++.h>
using namespace std;
#define endl "\n"
typedef long long LL;
typedef pair<int,int> PII;
#define MULINPUT
/*DATA & KEY
t 1 10
n 1 200000
k 0 1e9
ai 0 1e9
bi 0 1e9
*/
int T;
const int N=200005;
bool vis[N];
void solve(int C)
{
//NEW DATA CLEAN
vector<LL>a,b;
//NOTE!!!
LL n,k;scanf("%lld %lld",&n,&k);
for(int i=1;i<=n;i++)
{
LL x;scanf("%lld",&x);
a.push_back(x);
}
for(int i=1;i<=n;i++)
{
LL x;scanf("%lld",&x);
b.push_back(x);
}
sort(a.begin(),a.end());
sort(b.begin(),b.end());
LL ans=0;
for(int i=0;i<n;i++)
{
int l=0,r=b.size()-1;
while(l<r)
{
int mid=(l+r)>>1;
if(a[i]+b[mid]>=k)r=mid;
else l=mid+1;
}
if(a[i]+b[l]>=k)
{
ans++;
b.erase(b.begin()+l);
}
}
cout<<ans<<endl;
}
int main()
{
#ifdef MULINPUT
scanf("%d",&T);
for(int i=1;i<=T;i++)solve(i);
#else
solve(1);
#endif
return 0;
}
F - God of Gamblers
样例
Sample Input
1 0
3 3
Sample Output
1.00000
0.50000
题意
看了很久才看懂(英语能力=-INF)
每次有1/2赢的概率,一块钱开始,赢了拿两倍的钱,输了就没了本钱,每次数就翻倍,只要赢了就能拿回之前输的。
思路
直接找的规律hh,输出n/(m+n)即可
代码
#include<bits/stdc++.h>
using namespace std;
#define endl "\n"
typedef long long LL;
typedef pair<int,int> PII;
//#define MULINPUT
/*DATA & KEY
F
*/
int T;
void solve(int C)
{
//NEW DATA CLEAN
//NOTE!!!
double n,m;
while(~scanf("%lf%lf",&n,&m))
{
printf("%.5lf\n",n/(m+n));
}
}
int main()
{
#ifdef MULINPUT
scanf("%d",&T);
for(int i=1;i<=T;i++)solve(i);
#else
solve(1);
#endif
return 0;
}
J - LOL
样例
Sample Input
0110011100011001001100011110001110001110001010010111111110101010010011010000110100011001001111101011
1000111101111110110100001101001101010001111001001011110001111110101000011101000001011100001001011010
0100101100011110011100110110011100111100010010011001111110101111111000000110001110000110001100001110
1110010101010001000110100011101010001010000110001111111110101010000000001111001110110101110000010011
1000010011111110001101100000101001110100011000111010011111110110111010011111010110101111011111011011
Sample Output
515649254
题意
100个英雄,两只队伍两种操作,ban和pick
ban:每个人都可以随意选择英雄
pick:只能选自己有的
敌方有全部英雄,题目通过5个01串告诉你己方有的英雄
ban和pick的20个英雄不能重复
思路
选英雄应该用排列数,禁英雄应用组合数
考虑答案组成=敌方ban人方案数x敌方pick人方案数x己方ban人x己方pick人方案数
先考虑己方pick人方案数,这样其他的容易考虑
己方pick人方案数:爆搜+剪枝/DP
己方ban人方案数:C(95,5)
敌方ban人方案数:C(90,5)
敌方pick人方案数:A(85,5)
爆搜解法
思考如何爆搜,相当于根据给的5*100的01矩阵,每一行选1个,要求5个1不同列的方案数,如何实现呢?写个锤子DFS,直接循环一套加个条件判断啊!然后就是优化,正常来讲是要for循环五层的,这样铁定超时,我们可以把循环优化到4层,最后一层直接出结果即可
DP解法:
定义
d
p
[
i
]
[
j
]
dp[i][j]
dp[i][j]为选到第i个英雄的时候,己方队伍选择英雄情况(二进制表示)
状态转移有这么几种
5个人都不选第i个
d
p
[
i
]
[
j
]
+
=
d
p
[
i
−
1
]
[
j
]
dp[i][j]+=dp[i-1][j]
dp[i][j]+=dp[i−1][j]
第1个人选第i个
第2个人选第i个
第3个人选第i个
第4个人选第i个
第5个人选第i个
总结以下就是第k个人选第i个物品
i
f
(
第
k
个
人
没
选
过
并
且
这
个
人
可
以
选
i
)
d
p
[
i
]
[
j
]
=
d
p
[
i
−
1
]
[
j
−
(
1
<
<
k
)
]
if(第k个人没选过并且这个人可以选i)dp[i][j]=dp[i-1][j-(1<<k)]
if(第k个人没选过并且这个人可以选i)dp[i][j]=dp[i−1][j−(1<<k)]
j&(1<<i)==1表示第i个人已经选过了
j+(1<<i)表示让第i个人选当前英雄
代码
爆搜解法
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 105, mod = 1e9 + 7;
char a[N], b[N], c[N], d[N], e[N];
LL A(LL N, LL R) {
LL sum = 1;
for (int i = 0; i < R; i++) sum *= N - i;
return sum;
}
LL C(LL N, LL R) {
LL sum = 1;
for (int i = 1; i <= R; i++) sum = sum * (N + 1 - i) / i;
return sum;
}
int main() {
while (~scanf("%s%s%s%s%s", a, b, c, d, e)) {
int len = 0;
for (int i = 0; i < 100; i++)
if (e[i] == '1') len++;
LL ans = 0;
for (int i = 0; i < 100; i++) {
if (a[i] == '0') continue;
for (int j = 0; j < 100; j++) {
if (b[j] == '0' || j == i) continue;
for (int k = 0; k < 100; k++) {
if (c[k] == '0' || k == i || k == j) continue;
for (int p = 0; p < 100; p++) {
if (d[p] == '0' || p == i || p == j || p == k) continue;
int res = len;
if (e[i] == '1') res--;
if (e[j] == '1') res--;
if (e[k] == '1') res--;
if (e[p] == '1') res--;
if (res >= 0) ans += res;
}
}
}
}
cout << C(95, 5) * C(90, 5) % mod * A(85, 5) % mod * ans % mod<<endl;
}
return 0;
}
DP解法
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 105, M = 1 << 5, mod = 1e9 + 7;
char s[5][105];
LL dp[N][M];
LL A(LL N, LL R) {
LL sum = 1;
for (int i = 0; i < R; i++) sum *= N - i;
return sum;
}
LL C(LL N, LL R) {
LL sum = 1;
for (int i = 1; i <= R; i++) sum = sum * (N + 1 - i) / i;
return sum;
}
int main() {
while (
~scanf("%s%s%s%s%s", s[0] + 1, s[1] + 1, s[2] + 1, s[3] + 1, s[4] + 1)) {
memset(dp, 0, sizeof dp);
dp[0][0] = 1;
for (int i = 1; i <= 100; i++) //从前i个英雄里选
for (int j = 0; j < (1 << 5); j++) {
dp[i][j] += dp[i - 1][j]; //都不选
for (int k = 0; k < 5; k++)
if (j & (1 << k) && s[k][i] == '1') //可以选并且没选过
{
dp[i][j] = (dp[i][j] + dp[i - 1][j - (1 << k)]) % mod;
}
}
LL ans = dp[100][(1 << 5) - 1] % mod;
cout << C(95, 5) * C(90, 5) % mod * A(85, 5) % mod * ans % mod << endl;
}
return 0;
}