前言
赛前打几场重现赛模拟一下,争取把力所能及的题都补了
yysy,今年大部分赛站卷的程度已经非往年可比的了…
比赛链接:https://ac.nowcoder.com/acm/contest/24872
文章目录
题目一览
签到题:D,E,G,I
铜牌题:H
银牌题:J,K,M
金牌题:B
大概不是我能做的题:A,C,F,L
本场差不多是5题铜,6.5题银,8题金
D.Strange_Fractions(签到)
题意
思路
初中数学,设 x = a b x = \frac ab x=ba , 有 p q = x + 1 x \frac pq = x + \frac1x qp=x+x1 , 求根公式解出来即可。
E.Strange_Integerss(签到)
题意
从 n 个数中选出 m 个数使得两两之差绝对值不低于 k , 要求最⼤化 m 。
11 2
3 1 4 1 5 9 2 6 5 3 5
4
思路
排序后从⼩到⼤贪⼼选取合法且尽可能接近的数字即可
G.Edge Groups(树形dp)
题意
求树分解成若⼲⻓度为 的路径的⽅案数。
7
1 2
1 3
1 7
4 7
5 7
6 7
3
思路
很板子的树dp,队友写的,没看。官方题解:
I.Steadily Growing Steam(背包)
题意:
题目有点长,大意是:
n件物品具有体积 t i t_i ti 和价值 v i v_i vi ,选出⾄多 k k k 件物品 将其体积翻倍,然后选出若⼲物品并将其分为 体积和 相同的两堆 S , T S,T S,T,问选出的物品 价值之和 最⼤是多少。
输入
4 1
10 1
-5 3
5 1
6 1
输出
21
One possible scheme:
Double t 1 t_1 t1 and choose that S = { 1 } , T = { 3 , 4 } S=\{1\},T=\{3,4\} S={1},T={3,4}, where the point number sum are both 2, and the sum of the card values is 10 + 5 + 6 = 21 10+5+6=21 10+5+6=21.
思路:
很显然是01背包,其实挺签到的,但却把我们卡了一个多钟。
主要是一直在想两个集合怎么相互转移的问题。
后来想到两个集合是可以合并的。
我们假设装进集合S的物品体积为 + t i +t_i +ti , 那么可以假设装进集合T的物品体积为 − t i -t_i −ti ,这样动态转移的终点就会在体积和 V = 0 V=0 V=0 处了。
具体地,设 d p [ N ] [ V ] [ K ] dp[N][V][K] dp[N][V][K] 表示当前在第i个物品 , 体积和为V,已经将K件物品翻倍。
然后第 i i i个物品只会从第 i − 1 i-1 i−1 个物品转移,所以第一维的N可以用滚动数组滚掉,变成 d p [ 2 ] [ V ] [ K ] dp[2][V][K] dp[2][V][K].
我们将每个物品拆分成四个:
a a = ( v i , t i ) aa = (v_i , t_i) aa=(vi,ti) , b b = ( v i , − t i ) bb = (v_i ,- t_i) bb=(vi,−ti)
c c = ( v i , 2 ∗ t i ) , cc = (v_i , 2*t_i) , cc=(vi,2∗ti), d d = ( v i , − 2 ∗ t i ) dd = (v_i , -2*t_i) dd=(vi,−2∗ti)
那么:
d p [ i ] [ V ] [ K ] = m a x ( d p [ i ] [ V ] [ K ] , d p [ i ⊕ 1 ] [ V − a a ] [ j ] + v [ i ] ) dp[i][V][K] = max(dp[i][V][K],dp[i\oplus1][V-aa][j]+v[i]) dp[i][V][K]=max(dp[i][V][K],dp[i⊕1][V−aa][j]+v[i])
d p [ i ] [ V ] [ K ] = m a x ( d p [ i ] [ V ] [ K ] , d p [ i ⊕ 1 ] [ V − b b ] [ j ] + v [ i ] ) dp[i][V][K] = max(dp[i][V][K],dp[i\oplus1][V-bb][j]+v[i]) dp[i][V][K]=max(dp[i][V][K],dp[i⊕1][V−bb][j]+v[i])
d p [ i ] [ V ] [ K ] = m a x ( d p [ i ] [ V ] [ K ] , d p [ i ⊕ 1 ] [ V − c c ] [ j − 1 ] + v [ i ] ) dp[i][V][K] = max(dp[i][V][K],dp[i\oplus1][V-cc][j-1]+v[i]) dp[i][V][K]=max(dp[i][V][K],dp[i⊕1][V−cc][j−1]+v[i]) , K > 0 , K>0 ,K>0
d p [ i ] [ V ] [ K ] = m a x ( d p [ i ] [ V ] [ K ] , d p [ i ⊕ 1 ] [ V − d d ] [ j − 1 ] + v [ i ] ) dp[i][V][K] = max(dp[i][V][K],dp[i\oplus1][V-dd][j-1]+v[i]) dp[i][V][K]=max(dp[i][V][K],dp[i⊕1][V−dd][j−1]+v[i]) , K > 0 ,K>0 ,K>0
代码:
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N = 111;
int dp[2][5555][111];
int n,k;
int v[N],t[N];
signed main(){
ios::sync_with_stdio(false);
cin>>n>>k;
for(int i=1;i<=n;i++){
cin>>v[i]>>t[i];
}
for(int i=0;i<=5500;i++)
for(int j=0;j<=110;j++)
dp[0][i][j] = dp[1][i][j] = -1e15;
int now = 0,ans = 0;
dp[now][2800][0] = 0;
for(int i=1;i<=n;i++){
now = now^1;
int aa = t[i],bb = -t[i],cc = 2*t[i],dd = -2*t[i];
for(int V=-2600;V<=2600;V++){
for(int j=0;j<=k;j++){
int tmp = V+2800;
dp[now][tmp][j] = dp[now^1][tmp][j];
dp[now][tmp][j] = max(dp[now][tmp][j],dp[now^1][tmp-aa][j]+v[i]);
dp[now][tmp][j] = max(dp[now][tmp][j],dp[now^1][tmp-bb][j]+v[i]);
if(k>0){
dp[now][tmp][j] = max(dp[now][tmp][j],dp[now^1][tmp-cc][j-1]+v[i]);
dp[now][tmp][j] = max(dp[now][tmp][j],dp[now^1][tmp-dd][j-1]+v[i]);
}
ans = max(ans,dp[now][tmp][j]);
}
}
}
cout<<ans<<endl;
}
H.Life is a Game (kruskal重构树+树上倍增)
题意:
⼀张带边权带点权⽆向图。从某点出发,有初始声望。 每第⼀次到达⼀个点将获得点权等值的声望加成。
经过⼀条边需要满⾜边权等值的最低声望限制。 多次给出起点和初始声望,询问能达到的最⼤声望。
思路:
铜牌题越来越难了啊。
我们不会kruskal重构树,那天用堆+启发式合并硬搞出来的。
现在补题主要写一写kruskal重构树的解法,毕竟可以离线做这道题。
洛谷上的kruskal重构树:https://www.luogu.com.cn/problem/P7834 (不过洛谷这题加了主席树维护第k大)
其实就是在kruskal的过程中建树:
把边按边权从小到大排序,并查集合并两端点 u , v u,v u,v 的同时新建一个节点 t o t tot tot , 节点 t o t tot tot连接 u , v u,v u,v , 且维护 u , v u,v u,v点的共同信息
在本题中 , t o t tot tot 节点可以维护两个信息 , a u + a v a_u + a_v au+av 和 w ( u , v ) w(u,v) w(u,v) 。
本题的感想是kruskal重构树是一个很好的思路,它用很少的时间和空间维护了并查集的一些关键信息。
样例的重构树长这样:
然后树上倍增维护每个节点的第i级父节点,查询的时候倍增地查就好了。
官方解答:
代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N = 1e5+100;
struct node{
int u;
int v;
int w;
bool operator<(node B) const{
return w<B.w;
}
}e[N<<1];
int a[N<<1];
int n,m,q;
int fa[N<<1];
vector<int> g[N<<1];
int val[N<<1];
int ff[N<<1][22];
int tot;
int find_fa(int x){
return (fa[x]==x)?fa[x]:fa[x] = find_fa(fa[x]);
}
void Kruskal(){
tot = n;
for(int i=1;i<=m;i++){
int u = find_fa(e[i].u),v = find_fa(e[i].v),w = e[i].w;
if(find_fa(u)!= find_fa(v)){
tot++;
val[tot] = w;
g[tot].push_back(u);
g[tot].push_back(v);
g[u].push_back(tot);
g[v].push_back(tot);
fa[u] = fa[v] = fa[tot] = tot;
}
}
}
void dfs(int u,int f){
ff[u][0] = f;
for(int i=1;i<=20;i++){
ff[u][i] = ff[ff[u][i-1]][i-1];
}
for(auto v:g[u]){
if(v==f) continue;
dfs(v,u);
a[u] += a[v];
}
}
signed main(){
ios::sync_with_stdio(false);
cin>>n>>m>>q;
for(int i=1;i<=n;i++) cin>>a[i],fa[i] = i;
for(int i=1;i<=m;i++){
int x,y,z;
cin>>x>>y>>z;
e[i] = {x,y,z};
}
sort(e+1,e+1+m);
Kruskal();
dfs(tot,0);
val[0] = 1e15+7;
while(q--){
int u,x;
cin>>u>>x;
int now = x+a[u];
while(now!=tot){
//cout<<u<<" "<<x<<endl;
bool ok = false;
for(int i=20;i>=0;i--){
if(val[ff[u][i]]<=now){
u = ff[u][i];
ok = true;
}
}
if(!ok) break;
now = x+a[u];
}
cout<<now<<endl;
}
return 0;
}
K.Circle of Life(打表+构造)
题意
思路
找规律题,感觉如果把重构树写完还能剩一些时间的话大概率都能写写这题。。。
把题意模拟出来,然后发现是构造
发现n = 6时只有两种解:100110,(另一个忘了)
然后以这两个为主去找规律,发现1001可以作为循环节,然后没了。
代码:
#include<bits/stdc++.h>
using namespace std;
string s[4] = {"1001","10001","100110","1001010"};
int main(){
ios::sync_with_stdio(false);
int n;
cin>>n;
if(n==2){
cout<<"10"<<endl;
}
else if(n==3){
cout<<"Unlucky"<<endl;
}
else if(n<=7){
cout<<s[n-4]<<endl;
}
else{
int tot = (n-4)/4;
int res = (n-4)%4;
for(int i=0;i<tot;i++) cout<<"1001";
cout<<s[res]<<endl;
}
}
J.Two Binary Strings Problem(bitset+位运算)
题意:
输入
2
5
11010
11000
8
11110000
11111100
输出
01000
00001100
思路:
会不会用bitset决定了这题能不能写。。。。
很显然,打暴力的话复杂度是 O ( n 2 ) O(n^2) O(n2)
对于32位整型INT , 用bitset通过位运算打暴力的复杂度是 O ( n 2 32 ) O(\frac {n^2}{32}) O(32n2)
但是细节很多,对着逆十字的代码看了半天才弄明白。。。
把0变成-1,然后维护前缀和 s u m [ ] sum[] sum[]
把前缀和排个序,大的在前面
然后按顺序遍历一遍
于是惊奇的发现,如果前面访问的位置 i i i比之后访问的位置 j j j小,那么 j j j这个位置肯定是不行的
因为既然有 s u m [ i ] > s u m [ j ] sum[i] > sum[j] sum[i]>sum[j] 且 i < j i < j i<j
那么就必然存在一个 k k k ,使得 s u m [ j ] − s u m [ j − k ] < = 0 sum[j] - sum[j-k] <=0 sum[j]−sum[j−k]<=0 ,也就是 [ j − k , j ] [j-k,j] [j−k,j] 这个区间的0不比1少
所以开一个bitset A , 把顺序遍历时对应的位置pos标上,代表该位置被访问了。
对于 b [ i ] = 0 b[i] = 0 b[i]=0 的情况,其实就是将 b [ i ] = 1 b[i] = 1 b[i]=1时的各项取反
那么再开一个bitset one,置为全1 , 因为二进制数 异或 全1就是取反。
然后一个bitset ans 记录答案,每次遍历时拿bitset A 更新ans.
代码:
#include<bits/stdc++.h>
using namespace std;
const int N = 50500;
char a[N],b[N];
int s[N],id[N];
bitset<N> A,ans,one;
bool cmp(int xx,int yy){
return s[xx]==s[yy]?xx<yy:s[xx]>s[yy];
}
int main(){
ios::sync_with_stdio(false);
int t;
cin>>t;
while(t--){
A.reset(),ans.reset(),one.reset();
int n;
cin>>n;
cin>>(a+1);
cin>>(b+1);
int tg = n+1;
for(int i=1;i<=n;i++) one[i] = 1;
id[0] = 0;
for(int i=1;i<=n;i++){
s[i] = s[i-1]+((a[i]=='1')?1:-1);
id[i] = i;
}
sort(id,id+1+n,cmp);
for(int i=0;i<=n;i++){
int pos = id[i];
if(pos) {
if (b[pos] == '1') {
ans = ans | (A >> (n - pos));
if (s[pos] <= 0) tg = min(tg, pos + 1);
} else {
ans = ans | ((A ^ one) >> (n - pos));
if (s[pos] > 0) tg = min(tg, pos + 1);
}
}
A[n-pos] = 1;
}
for(int i=1;i<=n;i++){
if(ans[i]||i>=tg) cout<<0;
else cout<<1;
}
cout<<endl;
}
}
B.Strange Permutations (生成函数 / NTT+容斥)
呜呜呜,不会生成函数,不会快速傅里叶变换,不会数论变换
呜呜呜,我怎么什么都不会呀
有空再更吧。。。。