题目链接
http://codeforces.com/gym/102770
参考题解
A - AD 2020
简要题意:
多组数据,每次给定起始和结束日期 yyyymmdd,询问有多少天的日期表示中含有 202 子串。
解题思路:
日期总数为几百万,不大,可以直接预处理出来,含有 202 子串的日期权值为 1,否则为 0,每次询问就是区间和。更进一步,直接存权值为 1 的日期,询问时二分确定区间长度。
参考代码:
#include<bits/stdc++.h>
using namespace std;
#define pb emplace_back
#define sz(a) ((int)a.size())
typedef long long ll;
const int maxn = 5e6 + 5;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;
const int days[] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
int a[maxn];
int n;
inline int isLeap(int y){
return y % 400 == 0 || (y % 4 == 0 && y % 100 != 0);
}
inline int isBad(int x){
string s = to_string(x);
return s.find("202") != string::npos;
}
void init(){
for(int i = 2000; i <= 9999; ++i){
for(int j = 1; j <= 12; ++j){
int lim = days[j];
if(isLeap(i) && j == 2) ++lim;
for(int k = 1; k <= lim; ++k){
int x = i * 10000 + j * 100 + k;
if(isBad(x)) a[++n] = x;
}
}
}
}
int main(){
// ios::sync_with_stdio(0); cin.tie(0);
init();
// cout << n << endl;
int T; scanf("%d", &T);
while(T--){
int y1, m1, d1, y2, m2, d2;
scanf("%d%d%d%d%d%d", &y1, &m1, &d1, &y2, &m2, &d2);
int x = y1 * 10000 + m1 * 100 + d1;
int y = y2 * 10000 + m2 * 100 + d2;
int p1 = lower_bound(a + 1, a + 1 + n, x) - a;
int p2 = upper_bound(a + 1, a + 1 + n, y) - a;
// cout << p1 << " " << p2 << endl;
printf("%d\n", p2 - p1);
}
return 0;
}
B - Bin Packing Problem
简要题意:
给定 n n n 个物品,第 i i i 个体积为 a i a_i ai,现有容量为 C C C 的背包若干,按下标顺序存放物品,分别以 First Fit 和 Best Fit 策略进行,问分别需要多少背包。
解题思路:
使用数据结构模拟,对于 First Fit 策略,每次找到下标最小的剩余容量大于等于 a i a_i ai 的背包,由于有动态修改,使用线段树维护;对于 Best Fit 策略,每次找到最小的剩余容量大于等于 a i a_i ai 的背包,使用 multiset 进行 lower_bound 查找即可。
参考代码:
#include<bits/stdc++.h>
using namespace std;
#define pb emplace_back
#define sz(a) ((int)a.size())
#define lson (rt << 1)
#define rson (rt << 1 | 1)
#define gmid (l + r >> 1)
typedef long long ll;
const int maxn = 1e6 + 5;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;
int a[maxn];
int n, C;
struct SegTree{
int mx[maxn << 2];
void pushUp(int rt){
mx[rt] = max(mx[lson], mx[rson]);
}
void build(int l, int r, int rt){
mx[rt] = C;
if(l == r) return;
int mid = gmid;
build(l, mid, lson);
build(mid + 1, r, rson);
}
void update(int l, int r, int rt, int val){
if(l == r){
mx[rt] -= val;
return;
}
int mid = gmid;
if(mx[lson] >= val) update(l, mid, lson, val);
else update(mid + 1, r, rson, val);
pushUp(rt);
}
int query(int l, int r, int rt){
if(l == r) return mx[rt] == C ? l - 1 : l;
int mid = gmid;
if(mx[lson] == C) return query(l, mid, lson);
else return query(mid + 1, r, rson);
}
} tr;
int main(){
// ios::sync_with_stdio(0); cin.tie(0);
int T; scanf("%d", &T);
while(T--){
scanf("%d%d", &n, &C);
for(int i = 1; i <= n; ++i) scanf("%d", &a[i]);
tr.build(1, n, 1);
for(int i = 1; i <= n; ++i){
tr.update(1, n, 1, a[i]);
}
int ret1 = tr.query(1, n, 1);
int ret2 = 0;
multiset<int> st;
for(int i = 1; i <= n; ++i){
if(st.empty()){
st.insert(C - a[i]);
++ret2;
continue;
}
auto it = st.lower_bound(a[i]);
if(it == st.end()){
st.insert(C - a[i]);
++ret2;
continue;
}
int val = *it - a[i];
st.erase(it);
st.insert(val);
}
printf("%d %d\n", ret1, ret2);
}
return 0;
}
C - Crossword Validation
简要题意:
给定一个 n × n n×n n×n 的方格,每个格子为 # 或小写字母,方格含有的单词为所有水平或竖直方向的极长的字符串,再给定一个字典,含有 m m m 个有权值的单词,问是否方格含有的单词都在字典出现,若是则输出方格组成的单词的总权值。
解题思路:
注意单词总长为 4 × 1 0 6 4×10^6 4×106,哈希常数太大容易超时,故使用字典树存下字典中的单词,再作对应查询。
参考代码:
#include<bits/stdc++.h>
using namespace std;
#define pb emplace_back
#define sz(a) ((int)a.size())
#define lson (rt << 1)
#define rson (rt << 1 | 1)
#define gmid (l + r >> 1)
typedef long long ll;
const int maxn = 4e6 + 5;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;
char ss[1005][1005], s[maxn];
int nxt[maxn][26]; ll tag[maxn];
int n, m, cnt;
int add(){
++cnt; memset(nxt[cnt], 0, sizeof nxt[cnt]);
tag[cnt] = 0; return cnt;
}
void init(){
cnt = -1; add();
}
void insert(char *s, int val){
int p = 0;
while(*s){
int t = *s - 'a';
if(!nxt[p][t]) nxt[p][t] = add();
p = nxt[p][t], ++s;
}
tag[p] += val;
}
ll query(char *s){
int p = 0;
while(*s){
int t = *s - 'a';
if(!nxt[p][t]) return 0;
p = nxt[p][t], ++s;
}
return tag[p];
}
int main(){
// ios::sync_with_stdio(0); cin.tie(0);
int T; scanf("%d", &T);
while(T--){
scanf("%d%d", &n, &m);
for(int i = 1; i <= n; ++i) scanf("%s", ss[i] + 1);
init();
ll ret = 0;
for(int i = 1; i <= m; ++i){
int val; scanf("%s%d", s, &val);
insert(s, val);
}
for(int i = 1; i <= n; ++i){
for(int j = 1; j <= n; ++j){
if(ss[i][j] == '#') continue;
int p = 0;
while(j <= n && ss[i][j] != '#') s[p++] = ss[i][j], ++j;
s[p] = 0;
ll tmp = query(s);
if(!tmp) { ret = -1; break; }
ret += tmp;
}
if(ret == -1) break;
for(int j = 1; j <= n; ++j){
if(ss[j][i] == '#') continue;
int p = 0;
while(j <= n && ss[j][i] != '#') s[p++] = ss[j][i], ++j;
s[p] = 0;
ll tmp = query(s);
if(!tmp) { ret = -1; break; }
ret += tmp;
}
if(ret == -1) break;
}
printf("%lld\n", ret);
}
return 0;
}
E - Easy DP Problem
简要题意:
给一个二维 DP 的转移方程,多次询问某个 d p dp dp 值。转化后子问题为:每次询问区间的前 k k k 大的数的和。
解题思路:
转化的参考思路:将 d p dp dp 状态画成网格,询问 d p i , j dp_{i, j} dpi,j 的值,从转移方向来看,有 d p i − 1 , j dp_{i - 1, j} dpi−1,j 和 d p i − 1 , j − 1 + b i dp_{i - 1, j - 1} + b_i dpi−1,j−1+bi,每次 i i i 递减 1 1 1 并获得权值 i 2 i^2 i2,这部分是固定的,为 ∑ k = 1 i k 2 \sum\limits_{k = 1}^{i} k^2 k=1∑ik2。取 m a x max max 的部分,考虑从 ( i , j ) (i, j) (i,j) 走到 ( 0 , 0 ) (0, 0) (0,0),每次在 j j j 方向走则获得权值 b i b_i bi,故这部分最大值为 b i b_i bi 的前 j j j 大之和。
维护部分则用主席树,获取区间后,求靠右的 k k k 个数之和。
参考代码:
#include<bits/stdc++.h>
using namespace std;
#define pb emplace_back
#define sz(a) ((int)a.size())
#define lson (rt << 1)
#define rson (rt << 1 | 1)
#define gmid (l + r >> 1)
typedef long long ll;
const int maxn = 5e5 + 5;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;
int xi[maxn], cntX;
int a[maxn], num[maxn * 40], ls[maxn * 40], rs[maxn * 40], rt[maxn];
ll sum_p[maxn], sum[maxn * 40];
int n, q, tot;
void update(int l, int r, int &rt, int pre, int pos){
rt = ++tot; num[rt] = num[pre] + 1, sum[rt] = sum[pre] + xi[pos], ls[rt] = ls[pre], rs[rt] = rs[pre];
if(l == r) return;
int mid = gmid;
if(pos <= mid) update(l, mid, ls[rt], ls[pre], pos);
else update(mid + 1, r, rs[rt], rs[pre], pos);
}
ll query(int l, int r, int rt, int pre, int k){
if(l == r) return k * 1ll * xi[l];
int mid = gmid, d = num[rs[rt]] - num[rs[pre]];
if(d >= k) return query(mid + 1, r, rs[rt], rs[pre], k);
else return sum[rs[rt]] - sum[rs[pre]] + query(l, mid, ls[rt], ls[pre], k - d);
}
ll solve(int l, int r, int k){
ll ret = query(1, cntX, rt[r], rt[l - 1], k);
// cout << ret << " yyy" << endl;
return ret + sum_p[r - l + 1];
}
int main(){
// ios::sync_with_stdio(0); cin.tie(0);
sum_p[0] = 0;
for(int i = 1; i < maxn; ++i) sum_p[i] = sum_p[i - 1] + i * 1ll * i;
int T; scanf("%d", &T);
while(T--){
scanf("%d", &n);
for(int i = 1; i <= n; ++i) scanf("%d", &a[i]);
tot = cntX = 0;
for(int i = 1; i <= n; ++i){
xi[++cntX] = a[i];
rt[i] = 0;
}
sort(xi + 1, xi + 1 + cntX);
cntX = unique(xi + 1, xi + 1 + cntX) - xi - 1;
for(int i = 1; i <= n; ++i){
int x = lower_bound(xi + 1, xi + 1 + cntX, a[i]) - xi;
update(1, cntX, rt[i], rt[i - 1], x);
}
scanf("%d", &q);
while(q--){
int l, r, k; scanf("%d%d%d", &l, &r, &k);
ll ret = solve(l, r, k);
printf("%lld\n", ret);
}
}
return 0;
}
F - Finding a Sample
简要题意:
给定两个 n n n 维样本的二元分类器,参数为 ( w 1 , b 1 ) (\bold{w_1}, b_1) (w1,b1) 和 ( w 2 , b 2 ) (\bold{w_2}, b_2) (w2,b2),构造 x \bold{x} x 使得 ( w 1 T x + b 1 ) ( w 2 T x + b 2 ) < 0 (\bold{w_1^Tx} + b_1)(\bold{w_2^Tx} + b_2) < 0 (w1Tx+b1)(w2Tx+b2)<0。无解输出 No。
解题思路:
无解当且仅当分类器对应的两个超平面重合,即对应法向量 w \bold{w} w 同向且偏置 b b b 成对应比例,否则必定有解。对应解的构造,分类讨论即可,只需要两维来构造。
参考代码:
#include<bits/stdc++.h>
using namespace std;
#define pb emplace_back
#define sz(a) ((int)a.size())
#define lson (rt << 1)
#define rson (rt << 1 | 1)
#define gmid (l + r >> 1)
typedef long long ll;
const int maxn = 3e5 + 5;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;
#define eps 1e-7
const double lim = 1e4;
int a[maxn], b[maxn];
double xi[maxn];
int n;
int solve(){
for(int i = 1; i <= n; ++i) xi[i] = 0;
if(a[0] * b[0] < 0) return 1;
auto check = [](int ai, int a0, int bi, int b0, double &x) -> int{
auto getX = [](int ai, int a0) -> vector<double>{
vector<double> ax;
if(!ai) ax.pb(0);
else ax.pb(-1.0 * a0 / ai + eps), ax.pb(-1.0 * a0 / ai - eps);
return ax;
};
auto ax = getX(ai, a0), bx = getX(bi, b0);
auto cal = [](int ai, int a0, double x) -> double{
return ai * x + a0;
};
for(auto &v : ax) if(cal(ai, a0, v) * cal(bi, b0, v) < 0) { x = v; return 1; }
for(auto &v : bx) if(cal(ai, a0, v) * cal(bi, b0, v) < 0) { x = v; return 1; }
return 0;
};
for(int i = 1; i <= n; ++i) if(check(a[i], a[0], b[i], b[0], xi[i])) return 1;
if(!a[0] && !b[0]){
for(int i = 1; i <= n; ++i){
for(int j = 1; j <= n; ++j){
if(check(a[i], a[j], b[i], b[j], xi[i])) { xi[j] = 1; return 1; }
}
}
}
return 0;
}
int main(){
ios::sync_with_stdio(0); cin.tie(0);
cout << fixed << setprecision(8);
int T; cin >> T;
while(T--){
cin >> n;
for(int i = 1; i <= n; ++i) cin >> a[i]; cin >> a[0];
for(int i = 1; i <= n; ++i) cin >> b[i]; cin >> b[0];
if(!solve()) cout << "No" << "\n";
else{
for(int i = 1; i <= n; ++i) cout << xi[i] << " \n"[i == n];
}
}
return 0;
}
G - Gliding
简要题意:
三维空间里,给定起点 ( s x , s y , 0 ) (sx, sy, 0) (sx,sy,0) 和终点 ( t x , t y , 0 ) (tx, ty, 0) (tx,ty,0),水平速度为 v h v_h vh,竖直下落速度为 v f v_f vf,开滑翔伞后为 v p ( v p < v f ) v_p(v_p < v_f) vp(vp<vf)。同时有 n + 1 n + 1 n+1 个风洞 ( x i , y i , 0 ) (x_i, y_i, 0) (xi,yi,0),提供 v i v_i vi 的上升速度。 起点落在第 0 0 0 个风洞 ( v 0 > v p ) (v_0 > v_p) (v0>vp),问从起点到终点的最短时间。
解题思路:
首先滑翔伞一直开最优,其次 v i ≤ v p v_i \leq v_p vi≤vp 的风洞没用。从起点到终点,依次使用的风洞的 v v v 递增(否则可以原地上升,无需使用 v v v 更小的风洞)。从终点反向考虑,按 v v v 建立拓扑图, D P DP DP 最短路即可。
参考代码:
#include<bits/stdc++.h>
using namespace std;
#define pb emplace_back
#define sz(a) ((int)a.size())
#define lson (rt << 1)
#define rson (rt << 1 | 1)
#define gmid (l + r >> 1)
typedef long long ll;
const int maxn = 4e3 + 5;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;
#define eps 1e-11
const double oo = 1e20;
struct Node{
double x, y, v;
bool operator < (const Node &o) const{
return v < o.v;
}
} a[maxn];
struct Edge{
double w; int v;
};
vector<Edge> G[maxn];
double dp[maxn], vf, vp, vh;
int n, sx, sy, tx, ty;
#define sqr(x) ((x)*(x))
inline double calDis(int i, int j){
return sqrt(sqr(a[i].x - a[j].x) + sqr(a[i].y - a[j].y)) / vh;
}
inline int getS(){
for(int i = 0; i < n; ++i) if(a[i].x == sx && a[i].y == sy) return i;
return 0;
}
void build(){
for(int i = 0; i <= n; ++i) G[i].clear();
sort(a, a + n);
for(int i = n; i >= 1; --i){
if(a[i].v < vp) break;
for(int j = i - 1; j >= 0; --j){
if(a[j].v < vp) break;
double w = calDis(i, j);
w += w * vp / (a[j].v - vp);
// cout << i << " -> " << j << " " << w << endl;
G[i].pb(Edge{w, j});
}
}
}
double solve(){
build();
int sp = getS();
for(int i = 0; i <= n; ++i) dp[i] = oo;
dp[n] = 0;
for(int i = n; i >= 1; --i){
for(auto &e : G[i]){
dp[e.v] = min(dp[e.v], dp[i] + e.w);
}
}
return dp[sp];
}
int main(){
ios::sync_with_stdio(0); cin.tie(0);
cout << fixed << setprecision(11);
int T; cin >> T;
while(T--){
cin >> sx >> sy >> tx >> ty;
cin >> vf >> vp >> vh >> n;
for(int i = 0; i <= n; ++i){
cin >> a[i].x >> a[i].y >> a[i].v;
}
a[++n] = {tx, ty, 23333333};
cout << solve() << "\n";
}
return 0;
}
I - Invoking the Magic
简要题意:
给定 n n n 对袜子,开始不一定是配对的。每次魔法操作可以选择 k k k 对袜子进行两两自动匹配(必须完美匹配),问能将全部袜子匹配的最小 k k k 值。每种颜色的袜子有且仅有一对。
解题思路:
颜色范围很大,先进行离散化。以颜色为结点,初始配对状态为边建图。由于每种颜色袜子只有一对,每个点度数都为 2 2 2,那么每次魔法操作选择为一个连通块(环)。建图后求最大的连通块大小即可。
参考代码:
#include<bits/stdc++.h>
using namespace std;
#define pb emplace_back
#define sz(a) ((int)a.size())
#define lson (rt << 1)
#define rson (rt << 1 | 1)
#define gmid (l + r >> 1)
typedef long long ll;
const int maxn = 3e5 + 5;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;
vector<int> G[maxn];
int xi[maxn], cntX;
int a[maxn], b[maxn], vis[maxn];
int n, tot;
void dfs(int u){
++tot, vis[u] = 1;
for(auto &v : G[u]){
if(!vis[v]) dfs(v);
}
}
int main(){
// ios::sync_with_stdio(0); cin.tie(0);
int T; scanf("%d", &T);
while(T--){
scanf("%d", &n);
cntX = 0;
for(int i = 1; i <= n; ++i){
scanf("%d%d", &a[i], &b[i]);
xi[++cntX] = a[i];
xi[++cntX] = b[i];
}
sort(xi + 1, xi + 1 + cntX);
cntX = unique(xi + 1, xi + 1 + cntX) - xi - 1;
for(int i = 1; i <= cntX; ++i) G[i].clear();
for(int i = 1; i <= n; ++i){
a[i] = lower_bound(xi + 1, xi + 1 + cntX, a[i]) - xi;
b[i] = lower_bound(xi + 1, xi + 1 + cntX, b[i]) - xi;
G[a[i]].pb(b[i]), G[b[i]].pb(a[i]);
}
for(int i = 1; i <= cntX; ++i) vis[i] = 0;
int ret = 0;
for(int i = 1; i <= cntX; ++i){
if(!vis[i]) tot = 0, dfs(i), ret = max(ret, tot);
}
printf("%d\n", ret);
}
return 0;
}
K - Killing the Brute-force
简要题意:
签到题。
解题思路:
签到题。
参考代码:
#include<bits/stdc++.h>
using namespace std;
#define pb emplace_back
#define sz(a) ((int)a.size())
typedef long long ll;
const int maxn = 3e5 + 5;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;
int a[maxn], b[maxn];
int n;
int main(){
ios::sync_with_stdio(0); cin.tie(0);
int T; cin >> T;
while(T--){
cin >> n;
for(int i = 1; i <= n; ++i) cin >> a[i];
for(int i = 1; i <= n; ++i) cin >> b[i];
int ret = -1;
for(int i = 1; i <= n; ++i){
if(b[i] > 3 * a[i]) { ret = i; break; }
}
cout << ret << endl;
}
return 0;
}