2021 ICPC 上海 DEHI 链接:The 2021 ICPC Asia Shanghai Regional Programming Contest
D. Strange Fractions 题意:给你 ,让你找正整数 ,使得 。如果不存在,输出 。
思路:简单数学。推柿子+
因为有 ,我们通分一下就有:
于是: 我们移向得: 那么: 我们发现,2个方程2个未知数,可以解了。我直接求根公式算的。
接下来就是求解过程了: 然后把 代入得: 一开始以为这样就搞定了,很愉快的 了,然后
思考了一会,发现 和 的结果不一样。好的,它可能不是最简的。那么上下同除 化为最简之后在做,就可以 了。
注意:
可能不是最简分数,注意先化简
有精度问题,我们用二分解决
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 #include <bits/stdc++.h> using namespace std;typedef long long ll;const int N = 2e5 + 10 ;bool check (ll x) { if (x < 0 ) return false ; ll l = 0 , r = x; while (l < r) { ll mid = (l + r) >> 1 ; if (mid * mid >= x) r = mid; else l = mid + 1 ; } if (l * l == x) return true ; else return false ; } ll sq (ll x) { ll l = 0 , r = x; while (l < r) { ll mid = (l + r) >> 1 ; if (mid * mid >= x) r = mid; else l = mid + 1 ; } return l; } ll p, q; void solve () { cin>>p>>q; ll g = __gcd(p, q); p /= g, q /= g; if (!check (2 * q + p) || !check (2 * q + p - 4 * q)) { cout<<"0 0\n" ; return ; } ll a1 = (-sq (2 * q + p) + sq (2 * q + p - 4 * q)) / -2 ; ll a2 = (-sq (2 * q + p) - sq (2 * q + p - 4 * q)) / -2 ; if (a1 >= 1 && q % a1 == 0 && q / a1 >= 1 ) cout<<a1<<" " <<q / a1; else if (a2 >= 1 && q % a2 == 0 && q / a2 >= 1 ) cout<<a2<<" " <<q / a2; else cout<<"0 0" ; cout<<'\n' ; } int main () { ios::sync_with_stdio (false ); cin.tie (nullptr );cout.tie (nullptr ); int tc; cin>>tc; for (int i = 1 ; i <= tc; i++) solve (); return 0 ; }
E.Strange_Integers 题意:给你 个数的序列和一个参数 ,你从这 个数里面选 个,使得任意两个的差值的绝对值都大于等于 。问你最多可以选多少个数。
思路: +贪心
我们先排序,然后 一遍处理出来前缀最大值,然后直接贪心即可(能选就选)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 #include <bits/stdc++.h> using namespace std;typedef long long ll;const int N = 1e5 +10 ;int a[N],pre[N];int n,m;int main () { ios::sync_with_stdio (false );cin.tie (nullptr );cout.tie (nullptr ); cin>>n>>m; for (int i = 1 ; i <= n; i++) cin>>a[i]; sort (a+1 ,a+1 +n); for (int i = 1 ; i <= n; i++) pre[i] = max (pre[i-1 ],a[i]); if (m==0 ){ cout<<n<<endl; return 0 ; } int now = a[1 ]; int cnt = 1 ; for (int i = 2 ; i <= n; i++) { if (pre[i]-now>=m)cnt++,now = pre[i]; } cout<<cnt<<endl; return 0 ; }
H.Life is a Game 题意:给你 个点 条边的无向图。每个点有点权,每条边也有边权。我们有一个初始能量值,每经过一个点,可以获得当前点的点权,但是要经过一条边,需要我们当前的能力值大于这个边的边权才能走。给你起点和初始能量值,问你能量值最大可以是多少?
思路: 重构树+树上倍增
由于有边权的限制,当能量值大于当前边权才能走。那么对于一条简单路径,限制我们的是这条路径上的最大值。我们考虑最优策略,如果有多条路,肯定走最大值越小的路。那么想让最大值最小,考虑 重构最小生成树。重构完之后,任意两点的 就是这个路径上的最大值了。
我一开始的思路是:从当前给出的节点往上走,如果当前的值大于等于限制,我们就可以获得这个子树的所有点的权值。但是 了。考虑最坏的情况,二叉树退化成链,这样的话,就是跑的暴力了,肯定是会 的。那怎么办呢?
因为我们建的是 重构树,是个大根堆,考虑预处理出每个点往上跳 步的花费。这里花费是指【要跳到的节点的限制-当前节点子树的权值和】。也就是,当前已经可以走到这里了,那么以当前节点为根的子树的权值我都可以得到了,用我要跳到的点的限制-当前获得的,就是我们的额外的花费。我们用这个花费和 去比,如果 小就可以往上跳,直到不能跳了为止。那么答案就是你能跳到的点的权值和+初始值 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 #include <bits/stdc++.h> #define INF 0x3f3f3f3f #define int long long using namespace std;typedef long long ll;const int N = 4e5 +10 , M = 5e5 +10 ;const int LOGN = 20 ;struct Node { int x,y,v; }a[M+1 ]; int n,m,q,now,val[N],dep[N],faa[N];vector<int >e[N]; int ls[N],rs[N];int sum[N];int f[N][LOGN+2 ],cost[N][LOGN+2 ];int c[N];struct Dsu { int fa[N]; void init (int x) {for (int i = 1 ;i<=x;i++) fa[i] = i;} int find (int x) {return x == fa[x] ? x : fa[x] = find (fa[x]);} void merge (int x, int y) { int u = find (x), v = find (y); if (u == v) return ; fa[u] = v; } }dsu; void kruskalRebuildTree () { dsu.init (2 *n-1 ); sort (a+1 , a+1 +m, [](const Node& x, const Node& y) { return x.v < y.v; }); now = n; for (int i = 1 ;i<=m;i++) { ll u = dsu.find (a[i].x), v = dsu.find (a[i].y), w = a[i].v; if (u != v) { val[++now] = w; c[now] = c[u]+c[v]; cost[u][0 ] = val[now] - c[u]; cost[v][0 ] = val[now] - c[v]; f[u][0 ] = f[v][0 ] = now; dsu.merge (u, now); dsu.merge (v, now); ls[now] = u; rs[now] = v; } } } signed main () { ios::sync_with_stdio (false ); cin.tie (nullptr );cout.tie (nullptr ); cin>>n>>m>>q; for (int i = 1 ;i<=n;i++)cin>>c[i]; for (int i = 1 ;i<=m;i++) { cin>>a[i].x>>a[i].y>>a[i].v; } kruskalRebuildTree (); for (int j = 1 ;j<=LOGN;j++) { for (int u = 1 ;u<=now;u++) { f[u][j] = f[f[u][j-1 ]][j-1 ]; cost[u][j] = max (cost[u][j-1 ],cost[f[u][j-1 ]][j-1 ]); } } while (q--) { int x, k; cin>>x>>k; for (int j = LOGN;j>=0 ;j--) { if (cost[x][j]<=k&&f[x][j])x = f[x][j]; } cout<<c[x]+k<<"\n" ; } return 0 ; }
I.Steadily Growing Steam 题意:我们有 张卡片,每张卡片有自己的权值和点数。让你从这 张卡片里面选任意张,然后你可以令其中至多 张卡片的点数翻倍。之后我们把这选出来的卡片分成2组,使得这两组的点数一样。问:满足上述条件,两组的权值和最大可以是多少?
思路: (背包问题)
我们考虑定义 数组,很容易想到定义为 :表示前 个物品,两组卡片点数差值为 ,使用了 次翻倍的权值和的最大值。但是物品数有 ,点差值在 到 ,需要开到 ,次数 ,那么就需要开到$1002600 100的 数 组 。 并 且 点 数 的 差 值 有 负 数 存 在 , 那 么 我 们 需 要 一 个 偏 移 量 , 避 免 负 数 下 标 。 定 义 1300为 0即 偏 移 量 为 1300, 数 组 从 0, 到 2600, 就 能 表 示 -1300到 1300$所以数了。我们第一维还可以考虑滚动数组优化。
考虑状态的转移:
不加入第 张牌,那么
加入第 张牌:
滚动数组优化:Misplaced & i&1 表示上一轮的。
不用魔法翻倍:
考虑放在左边:Misplaced & if(j-w[i]\ge 0) f[i& 1][j][k] = max(f[i & 1][j][k],f[(i-1) & 1][j-w[i]][k]+v[i])
考虑放在右边:Misplaced & if(j+w[i]<=2600)f[i& 1][j][k] = max(f[i& 1][j][k],f[(i-1)& 1][j-w[i]][k]+v[i])
考虑用魔法翻倍:
考虑放在左边:$if(j-2w[i]>=0 &&k >0)f[i& 1][j][k] = max(f[i& 1][j][k],f[(i-1)& 1][j-2 w[i]][k-1]+v[i])$
考虑放在右边:$if(j+2w[i]<=2600&&k>0)f[i& 1][j][k] = max(f[i& 1][j][k],f[(i-1)& 1][j+2 w[i]][k-1]+v[i])$
最后因为要求两边点数一样,那么差值就是 ,我们规定的偏移量是 ,那么答案就是在 处的值。
注意:记得初始化为-INF。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 #include <bits/stdc++.h> using namespace std;typedef long long ll;ll f[2 ][2610 ][110 ]; ll v[110 ], w[110 ]; ll n, m; int main () { ios::sync_with_stdio (false );cin.tie (nullptr );cout.tie (nullptr ); cin>>n>>m; for (int i = 1 ;i <= n; i++) cin>>v[i]>>w[i]; for (int i = 0 ;i <= 1 ;i ++) for (int j = 0 ;j <= 2600 ; j++) for (int k = 0 ;k <= m; k++) f[i][j][k] = -1e18 ; f[0 ][1300 ][0 ] = f[1 ][1300 ][0 ] = 0 ; for (int i = 1 ;i <= n;i++) { for (int j = 0 ;j <= 2600 ; j++) { for (int k = 0 ; k <= m; k++) { f[i&1 ][j][k] = max (f[i&1 ][j][k],f[(i-1 )&1 ][j][k]); if (j-w[i]>=0 )f[i&1 ][j][k] = max (f[i&1 ][j][k],f[(i-1 )&1 ][j-w[i]][k]+v[i]); if (j+w[i]<=2600 )f[i&1 ][j][k] = max (f[i&1 ][j][k],f[(i-1 )&1 ][j+w[i]][k]+v[i]); if (j-2 *w[i]>=0 &&k>0 )f[i&1 ][j][k] = max (f[i&1 ][j][k],f[(i-1 )&1 ][j-2 *w[i]][k-1 ]+v[i]); if (j+2 *w[i]<=2600 &&k>0 )f[i&1 ][j][k] = max (f[i&1 ][j][k],f[(i-1 )&1 ][j+2 *w[i]][k-1 ]+v[i]); } } } ll ans = 0 ; for (int j = 0 ;j<= m;j++) ans = max (ans,f[n&1 ][1300 ][j]); cout<<ans<<"\n" ; return 0 ; }