2021 ICPC 上海 DEHI

nannandbk / 2023-08-15 / 原文

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}\)

于是:

\[\dfrac{p}{q} = \dfrac{a^2+b^2}{ab} = \dfrac{a^2+b^2+2ab-2ab}{ab} = \dfrac{(a+b)^2-2ab}{ab} = \dfrac{(a+b)^2}{ab}-2 \]

我们移项得:

\[\dfrac{p}{q}+2 = \dfrac{(a+b)^2}{ab} \]

\[\dfrac{p+2q}{q} = \dfrac{(a+b)^2}{ab} \]

那么:

\[p+2q = (a+b)^2 \]

\[q = ab \]

我们发现,2个方程2个未知数,可以解了。我直接求根公式算的。

接下来就是求解过程了:

\[a+b = \sqrt{p+2q} \]

\[b = \sqrt{p+2q}-a \]

然后把\(b\)代入得:

\[ab = a(\sqrt{p+2q}-a) = q \]

\[-a^2+a\sqrt{p+2q}-q = 0 \]

\[a = \dfrac{-\sqrt{p+2q}\pm\sqrt{p+2q-4q}}{-2} \]

\[b = \dfrac{q}{a} \]

一开始以为这样就搞定了,很愉快的\(submit\)了,然后\(wa...\)

思考了一会,发现\(p = 2,q = 1\)\(p = 4,q = 2\)的结果不一样。好的,它可能不是最简的。那么上下同除\(\gcd\)化为最简之后在做,就可以\(ac\)了。

注意:

  1. 可能不是最简分数,注意先化简
  2. \(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\)所以数了。我们第一维还可以考虑滚动数组优化。

考虑状态的转移:

  1. 不加入第\(i\)张牌,那么\(f[i][j][k] = f[i-1][j][k]\)

  2. 加入第\(i\)张牌:

    滚动数组优化:\(i\&1\)表示上一轮的。

    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])\)

    2. 考虑用魔法翻倍:

      考虑放在左边:\(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;
}