• 博客园logo
  • 会员
  • 众包
  • 新闻
  • 博问
  • 闪存
  • 赞助商
  • HarmonyOS
  • Chat2DB
    • 搜索
      所有博客
    • 搜索
      当前博客
  • 写随笔 我的博客 短消息 简洁模式
    用户头像
    我的博客 我的园子 账号设置 会员中心 简洁模式 ... 退出登录
    注册 登录
nannandbk
博客园    首页    新随笔    联系   管理    订阅  订阅
2021 ICPC 上海 DEHI

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;
}
posted on 2023-08-15 20:00  nannandbk  阅读(201)  评论(1)    收藏  举报
刷新页面返回顶部
博客园  ©  2004-2025
浙公网安备 33010602011771号 浙ICP备2021040463号-3