2021 ICPC 上海 DEHI
链接:The 2021 ICPC Asia Shanghai Regional Programming Contest
D. Strange Fractions
题意:给你\(p,q\),让你找正整数\(a,b\),使得\(\dfrac{p}{q} = \dfrac{a}{b}+\dfrac{b}{a}\)。如果不存在,输出\(0\) \(0\)。
思路:简单数学。推柿子+\(\gcd\)
因为有\(\dfrac{p}{q} = \dfrac{a}{b}+\dfrac{b}{a}\),我们通分一下就有:\(\dfrac{p}{q} = \dfrac{a^2+b^2}{ab}\)
于是:
我们移项得:
那么:
我们发现,2个方程2个未知数,可以解了。我直接求根公式算的。
接下来就是求解过程了:
然后把\(b\)代入得:
一开始以为这样就搞定了,很愉快的\(submit\)了,然后\(wa...\)
思考了一会,发现\(p = 2,q = 1\)和\(p = 4,q = 2\)的结果不一样。好的,它可能不是最简的。那么上下同除\(\gcd\)化为最简之后在做,就可以\(ac\)了。
注意:
- 可能不是最简分数,注意先化简
- \(sqrt\)有精度问题,我们用二分解决
#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
题意:给你\(n\)个数的序列和一个参数\(k\),你从这\(n\)个数里面选\(m\)个,使得任意两个的差值的绝对值都大于等于\(k\)。问你最多可以选多少个数。
思路:\(sort\)+贪心
我们先排序,然后\(for\)一遍处理出来前缀最大值,然后直接贪心即可(能选就选)。
#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
题意:给你\(n\)个点\(m\)条边的无向图。每个点有点权,每条边也有边权。我们有一个初始能量值,每经过一个点,可以获得当前点的点权,但是要经过一条边,需要我们当前的能力值大于这个边的边权才能走。给你起点和初始能量值,问你能量值最大可以是多少?
思路:\(Kruskal\)重构树+树上倍增
由于有边权的限制,当能量值大于当前边权才能走。那么对于一条简单路径,限制我们的是这条路径上的最大值。我们考虑最优策略,如果有多条路,肯定走最大值越小的路。那么想让最大值最小,考虑\(Kruskal\)重构最小生成树。重构完之后,任意两点的\(lca\)就是这个路径上的最大值了。
我一开始的思路是:从当前给出的节点往上走,如果当前的值大于等于限制,我们就可以获得这个子树的所有点的权值。但是\(T7\)了。考虑最坏的情况,二叉树退化成链,这样的话,就是跑的暴力了,肯定是会\(T\)的。那怎么办呢?
因为我们建的是\(Kruskal\)重构树,是个大根堆,考虑预处理出每个点往上跳\(2^j\)步的花费。这里花费是指【要跳到的节点的限制-当前节点子树的权值和】。也就是,当前已经可以走到这里了,那么以当前节点为根的子树的权值我都可以得到了,用我要跳到的点的限制-当前获得的,就是我们的额外的花费。我们用这个花费和\(k\)去比,如果\(\le k\)小就可以往上跳,直到不能跳了为止。那么答案就是你能跳到的点的权值和+初始值\(k\)。
#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];//以i为根的子树的权值和
//////////////////////////////DSU//////////////////////////////////////
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;
/////////////////////////Kruskal////////////////////////////////////
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;
}
}
}
////////////////////////////Main///////////////////////////////////
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
题意:我们有\(n\)张卡片,每张卡片有自己的权值和点数。让你从这\(n\)张卡片里面选任意张,然后你可以令其中至多\(k\)张卡片的点数翻倍。之后我们把这选出来的卡片分成2组,使得这两组的点数一样。问:满足上述条件,两组的权值和最大可以是多少?
思路:\(DP\)(背包问题)
我们考虑定义\(dp\)数组,很容易想到定义为\(f[i][j][k]\):表示前\(i\)个物品,两组卡片点数差值为\(j\),使用了\(k\)次翻倍的权值和的最大值。但是物品数有\(100\),点差值在\(-1300\)到\(1300\),需要开到\(2600\),次数\(100\),那么就需要开到\(100*2600*100\)的数组。并且点数的差值有负数存在,那么我们需要一个偏移量,避免负数下标。定义\(1300\)为\(0\)(即偏移量为\(1300\)),数组从\(0\),到\(2600\),就能表示\(-1300\)到\(1300\)所以数了。我们第一维还可以考虑滚动数组优化。
考虑状态的转移:
-
不加入第\(i\)张牌,那么\(f[i][j][k] = f[i-1][j][k]\)
-
加入第\(i\)张牌:
滚动数组优化:\(i\&1\)表示上一轮的。
-
不用魔法翻倍:
考虑放在左边:\(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])\)
考虑放在右边:\(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])\)
-
最后因为要求两边点数一样,那么差值就是\(0\),我们规定的偏移量是\(1300\),那么答案就是在\(1300\)处的值。
注意:记得初始化为-INF。
#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;
}