1. 卡牌
题意:
一共
n
n
n 种卡牌,每种卡牌
a
i
a_i
ai 张。
如果有
n
n
n 张卡牌,每张卡牌各有一张,则称为一套牌。
有
m
m
m 张空白牌,可以写上数字
i
i
i 充当第
i
i
i 种牌。第
i
i
i 种牌最多手写
b
i
b_i
bi 张。
问,最多能凑出多少套牌?
n
≤
2
×
1
0
5
;
a
i
,
b
i
≤
n
;
m
≤
n
2
n ≤ 2 × 10^5;\ a_i , b_i ≤ n;\ m ≤ n^2
n≤2×105; ai,bi≤n; m≤n2
思路
二分答案
可以看出最后的答案具有单调性,如果小答案满足不了的话大答案一定也满足不了。所以可以二分答案。
对于当前答案
m
i
d
mid
mid,遍历所有种类牌,尽可能的凑够
m
i
d
mid
mid。
如果最后所有种类的牌数都至少有
m
i
d
mid
mid 个,说明满足;否则不满足。
Code
#include<bits/stdc++.h>
using namespace std;
const int N = 200010;
int n;
long long m;
int a[N], b[N];
bool check(int mid)
{
long long sum = 0;
for(int i=1;i<=n;i++)
{
if(a[i] < mid)
{
int left = mid - a[i];
if(left > b[i]) return 0;
sum += left;
}
}
if(sum > m) return 0;
return 1;
}
int main(){
scanf("%d%lld", &n, &m);
for(int i=1;i<=n;i++) scanf("%d", &a[i]);
for(int i=1;i<=n;i++) scanf("%d", &b[i]);
int l = 1, r = 4*n;
while(l<r)
{
int mid = l+r+1>>1;
if(check(mid)) l = mid;
else r = mid-1;
}
printf("%d", l);
return 0;
}
2. 最大数字
题意
给定一个正整数
N
N
N。可以对其任意一位数字执行任意次以下操作:
-
将该位数字加 1。如果该位数字已经是 9,加 1 之后变成 0。
-
将该位数字减 1。如果该位数字已经是 0,减 1 之后变成 9。
总共可以执行操作 1 不超过
A
A
A 次,操作 2 不超过
B
B
B 次。
问最大可以将
N
N
N 变成多少?
1 ≤ N ≤ 1 0 17 ; 0 ≤ A , B ≤ 100 1 ≤ N ≤ 10^{17}; 0 ≤ A, B ≤ 100 1≤N≤1017;0≤A,B≤100
思路
dfs + 贪心
因为数据范围很小,一共只有17位,所以可以对每一位判断是进行1操作还是2操作。
为了使得最后的数尽可能大,只要剩余增加数的操作就增加数,直到变成9;但是对于减少数,只有能够减少为9的时候才用,减不到就不用。
Code
#include<bits/stdc++.h>
using namespace std;
const int N = 200010, mod = 1e9+7;
int T, n, m;
string s, ans;
int x, y;
void dfs(int u)
{
if(u == s.size())
{
if(s > ans) ans = s;
return;
}
char c = s[u];
int t = min(x, 9-(c-'0'));
s[u] = char(c+t);
x -= t;
dfs(u+1);
x += t;
s[u] = c;
if(y >= (c-'0')+1)
{
y -= (c-'0')+1;
s[u] = '9';
dfs(u+1);
y += (c-'0')+1;
s[u] = c;
}
dfs(u+1);
}
signed main(){
cin >> s >> x >> y;
dfs(0);
cout << ans;
return 0;
}
赛场上错的地方在于,加操作也只有至少 9-x 时才用,然后最后剩下了再补。
但是可能存在这样的情况,在第 i 位时不够 9-s[i],但是在第 i+1 位时够 9-s[i+1],这样就用到了第 i+1 位上,把这一位变成了9,但实际上将 i 位加上一个数贡献更大。
所以这种做法就是错误的。(对拍真有用啊
3. 出差
题意
一共
N
N
N 个城市,编号
1
N
1~N
1 N,每个城市有隔离时间
C
i
C_i
Ci。
给出
M
M
M 条双向路线
u
−
v
u-v
u−v,每条路线有通行时间
c
c
c。
问,从城市 1 到达城市
N
N
N,最少需要多少时间?(终点城市到达即可,不用算上隔离时间)
1
≤
N
≤
1000
,
1
≤
M
≤
10000
,
1
≤
C
i
≤
200
,
1
≤
u
,
v
≤
N
,
1
≤
c
≤
1000
1 ≤ N ≤ 1000 , 1 ≤ M ≤ 10000, 1 ≤ Ci ≤ 200, 1 ≤ u, v ≤ N, 1 ≤ c ≤ 1000
1≤N≤1000,1≤M≤10000,1≤Ci≤200,1≤u,v≤N,1≤c≤1000
思路
最短路
从节点 x
到节点 tx
需要的时间为:道路通行时间
+ tx 的隔离时间
。
Code
#include<bits/stdc++.h>
using namespace std;
#define PII pair<int, int>
const int N = 10010;
int n, m;
int a[N];
vector<pair<int,int> > e[N];
int dist[N], f[N];
void dij()
{
priority_queue<PII, vector<PII>, greater<PII> > que;
que.push(make_pair(0, 1));
for(int i=1;i<=n;i++) dist[i] = 0x3f3f3f3f;
dist[1] = 0;
while(que.size())
{
int x = que.top().second;
que.pop();
if(f[x]) continue;
f[x] = 1;
for(int i=0;i<e[x].size();i++)
{
PII it = e[x][i];
int tx = it.first, dis = it.second;
int t = 0;
if(tx != n) t = a[tx];
if(dist[tx] > dist[x] + dis + t){
dist[tx] = dist[x] + dis + t;
que.push(make_pair(dist[tx], tx));
}
}
}
}
int main(){
scanf("%d%d", &n, &m);
for(int i=1;i<=n;i++) scanf("%d", &a[i]);
while(m--){
int x, y, z;scanf("%d%d%d", &x, &y, &z);
e[x].push_back(make_pair(y, z));
e[y].push_back(make_pair(x, z));
}
dij();
printf("%d", dist[n]);
return 0;
}
场上不知道抽了哪根筋了,把 0x3f3f3f3f
记成了 0x3f
,g!
4. 费用报销
题意
给定
n
n
n 张票据的时间
d
a
y
day
day 和金额
v
a
l
val
val。
现需要选出若干张票据,要求其中任意两张的时间差不小于
K
K
K 天,总金额不超过
M
M
M。
问,选出的票据总金额最大为多少?
思路
01背包变式
把经典01背包中的体积和价值都看作金额,那么这题就是:
从
n
n
n 个物品中选,求总体积不超过
M
M
M 的条件下能够得到的最大价值。
定义状态:f[i, j]
表示从前 i
个物品中选,总金额不超过 j
的条件下得到的最大金额。
不同的是在更新时,需要保证当前物品要从时间差不小于
K
K
K 天的物品转移。
将所有物品按时间从小到大排序。
因为状态定义的是从前 i 个物品中选,所以位置越靠后答案越优,所以每次找最后一个满足时间差不小于 K 天的位置来转移。
可以看出,从前到后遍历所有物品,往前对应的转移位置是单调往后走的,所以可以用一个指针指向其位置,能往后走时就往后走。这样便可以省掉遍历转移位置的这重循环。
Code
#include<bits/stdc++.h>
using namespace std;
const int N = 10010;
int n, m, k;
int mon[13] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
int sum[N];
int f[1010][5010];
struct node{
int day, w;
}a[N];
bool cmp(node a, node b){
return a.day < b.day;
}
int main(){
cin >> n >> m >> k;
for(int i=1;i<=12;i++) sum[i] = sum[i-1]+mon[i];
for(int i=1;i<=n;i++)
{
int month, day, w;
cin >> month >> day >> w;
a[i].day = sum[month-1] + day;
a[i].w = w;
}
sort(a+1, a+n+1, cmp);
int t = 0;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
while(a[i].day - a[t+1].day >= k && t+1 < i) t++;
f[i][j] = f[i-1][j];
if(j>=a[i].w)
f[i][j] = max(f[i][j], f[t][j-a[i].w] + a[i].w);
}
}
cout << f[n][m];
return 0;
}
赛场上错的地方在于,f[i][j] = f[i-1][j]
,而不是 f[t][j]
。
因为状态定义的是前i个物品中的最大值,所以 f[i, j]
需要初始化为 f[i-1, j]
。后面转移的时候才用到 t
。
7. 齿轮
题意
给定一个长度为
n
n
n 的数组
a
[
]
a[]
a[]。
共
q
q
q 次询问,每次询问给出一个数
x
x
x,判断数组中是否存在两个数使得两数之商为
x
x
x ?
n
,
a
i
,
q
,
x
<
2
e
5.
n, a_i, q, x < 2e5.
n,ai,q,x<2e5.
思路
埃式筛
数据范围很大,询问的次数很多,所以基本上是要预处理出来答案。
转换一下思维,不去遍历两个位置求商,而是遍历数组出现的数 i
,然后遍历其所有倍数 j
,如果倍数 j
在数组中出现过,那么把 j/i
标记下来。
O
(
n
l
o
g
n
)
O(nlogn)
O(nlogn)。
最后
O
(
1
)
O(1)
O(1) 处理询问。
Code
#include<bits/stdc++.h>
using namespace std;
const int N = 200010, mod = 1e9+7;
int T, n, m;
int a[N];
int f[N];
bool ans[N];
void init()
{
for(int i=1;i<=2e5;i++)
{
if(!f[i]) continue;
for(int j=i;j<=2e5;j+=i)
{
if(f[j]) ans[j/i] = 1;
}
}
}
signed main(){
cin >> n >> m;
for(int i=1;i<=n;i++) cin >> a[i], f[a[i]] = 1;
init();
while(m--)
{
int x;cin >> x;
if(ans[x]) cout << "YES\n";
else cout << "NO\n";
}
return 0;
}
场上也看样例猜到了这种题意转换,但是没想到正解,暴力写的,对于每次询问都遍历一遍,判断乘积是否出现过。但是乘积的大小最大到4e10,然后直接搞到数组中re了。。
8. 搬砖
题意
给定
n
n
n 块砖,对于第
i
i
i 块砖有重量
w
i
w_i
wi,价值
v
i
v_i
vi 。
现在要选出若干块砖,按照一定顺序摞成一竖条。
要满足:对于其中的每块砖,其上面所有砖重量之和不能超过其自身价值。
问,这样选出的砖总价值最大为多少?
思路
贪心 + 01背包
很像之前做过的一道贪心题,耍杂技的牛。
定义状态 f[i, j]
表示,从前 i
个物品中选,总重量不超过 j
的条件下,能够得到的最大总价值。
起初这道题是直接按照价值从小到大排序,然后跑01背包,更新的时候注意满足总价值 k
减去当前物品体积不超过当前物品价值:k - a[i].wht <= a[i].val
。
但是不对,需要将所有物品贪心的按照 重量+价值 从小到大排序后再跑。不知道为什么。。(有知道的大佬评论一下~
Code
#include<bits/stdc++.h>
using namespace std;
const int N = 1010, mod = 1e9+7;
int T, n, m;
struct node{
int wht, val;
}a[N];
int f[N][20*N];
bool cmp(node a, node b){
return a.val + a.wht < b.val + b.wht;
}
signed main(){
cin >> n;
for(int i=1;i<=n;i++) cin >> a[i].wht >> a[i].val, m += a[i].wht;
sort(a+1, a+n+1, cmp);
int ans = 0;
for(int i=1;i<=n;i++)
{
for(int k=1;k<=m;k++)
{
f[i][k] = max(f[i][k], f[i-1][k]);
if(k >= a[i].wht && k - a[i].wht <= a[i].val)
f[i][k] = max(f[i][k], f[i-1][k-a[i].wht] + a[i].val);
ans = max(ans, f[i][k]);
}
}
cout << ans;
return 0;
}
场上最后没时间了,直接暴力搞的,但是暴力的思路都错了。。
这次打的太糟了,很多不该犯的错误都出来了,赛前没有花时间准备,手感和敏感都下降了,甚至连板子都忘了。。
以后像这样的比赛前,一定提前做好准备,考前至少模拟一套真题!
哎,长记性了。