ICPC2023南京个人题解

I. Counter

题意:给定一个初始值为零的计数器,每次操作可以使值+1或者变为0,再给定 \(m\) 个特定时间 \(a_i\) 的对应计数器的值 \(b_i\) ,问有没有可能的长度为 \(n\) 的操作序列满足所有条件。

限制条件:\(1\le a_i \le n \le 10^9,1\le m \le 10^5,1\le b_i \le 10^9\)

题解:根据给的要求,如果 \(a_i\) 时计数器为 \(b_i\),那么 \(a_i-b_i\) 时计数器必为0,并且 \([a_i-b_i+1,a_i]\) 时间内计数器的值都为1.所以只需要把所有的条件按照时间从小到大排序,然后依次check跟前一个是否矛盾就可以了。时间复杂度 \(O(m\log{m})\)

点击查看代码
#include<bits/stdc++.h>
using namespace std;
using ll=long long;
struct node
{
	ll a,b;
	friend bool operator < (node a,node b)
	{
		return a.a<b.a;
	}
};
int main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);
	ll tttt=1;
	cin>>tttt;
	while(tttt--)
	{
		ll n,m;
		cin>>n>>m;
		vector<node>c(m+7,{0,0});
		for(int i=1;i<=m;i++)
		{
			cin>>c[i].a>>c[i].b;
		}
		sort(c.begin()+1,c.begin()+m+1);
		bool yw=1;
		ll pre=0;
		for(int i=1;i<=m;i++)
		{
			if(c[i].a-c[i].b<pre||
			(c[i].a-c[i].b>pre&&c[i].a-c[i].b<=c[i-1].a))yw=0;
			pre=c[i].a-c[i].b;
		}
		cout<<(yw?"Yes\n":"No\n");
	}
	return 0;
}

F. Equivalent Rewriting

题意:有 \(n\) 个操作和 \(m\) 个位置,按照1~n的顺序来执行这些操作,第 \(i\) 个操作表示为将其中 \(p_i\) 个位置的值变成 \(i\) 。问有没有存在一种其他顺序的操作序列使得最终结果和1~n的结果一致,并输出。

限制条件:\(1\le n,m \le 10^5,1\le p_i \le m\)

题解:每个位置最终的值只和最后一个对它进行的操作有关。 所以如果我们交换了两个操作a,b的顺序,假设 \(b>a\) ,那么如果有某个位置最后是由b进行修改的,a和b中间就不能有其他修改过b的操作。那么如果某个a,b是可以交换的,并且 \(a\le b-1\) ,那么显然我们也可以交换b-1和b;反之,如果 b-1和b不能交换,我们也可以推出任意 \(a\le b-1\) 也是不能和b交换的。所以我们只需要检查所有相邻的两个操作是否能够交换即可,如果找到了一个直接交换这个位置然后输出答案就行了。

考虑一下怎么检查 \(b-1\)\(b\) 是否能够交换位置。先找出它们操作的数的交集,然后看这些交集里面有没有以 \(b\) 为最后一次操作的位置,如果有就不可以。然后就完了( ̄﹃ ̄)。时间复杂度 \(O(n+\sum_{i=1}^{n}p_i)\)

点击查看代码
#include<bits/stdc++.h>
using namespace std;
using ll=long long;

int main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);
	ll tttt=1;
	cin>>tttt;
	while(tttt--)
	{
		ll n,m;cin>>n>>m;
		vector<ll>num(m+7,0);
		vector<vector<ll>>c(n+7);
		for(int i=1;i<=n;i++)
		{
			ll p;cin>>p;
			while(p--)
			{
				ll a;
				cin>>a;
				num[a]=i;
				c[i].push_back(a);
			}
		}
		bool yw=0;
		vector<ll>ans(n+7);
		for(int i=1;i<=n;i++)
		{
			ans[i]=i;
		}
		for(int i=1;i<n;i++)
		{
			vector<bool>vis(m+7,0);
			bool ok=1;
			for(auto j:c[i])vis[j]=1;
			for(auto j:c[i+1])
			{
				if(vis[j]&&num[j]==i+1)
				{
					ok=0;
					break;
				}
			}
			if(!ok)continue;
			yw=1;
			swap(ans[i],ans[i+1]);
			break;
		}
		if(yw)
		{
			cout<<"Yes\n";
			for(int i=1;i<=n;i++)
			{
				cout<<ans[i]<<" \n"[i==n];
			}
		}
		else	cout<<"No\n";
		
	}
	return 0;
}

C. Primitive Root

题意:给定质数 \(P\) 和非负数 \(m\) ,问在0到 \(m\) 内的整数 \(g\) 有多少个满足\(g\oplus (P-1)\equiv 1\pmod{P}\)

限制条件:\(2\le P \le 10^{18},0\le m \le 10^{18},P 是质数\)

题解:化简一下式子先,题目等价于 \(g \oplus (P-1)=1+kP\),k是一个非负的整数。所以有 \(g=(1+kP)\oplus(P-1)\) ,并且显然不同的k对应的g的值是不等的,那就是求有几个非负整数k满足这个式子的范围在m以内。

其实异或是有上下限的,\((1+kP)-(P-1)\le g \le (1+kP)+(P-1)\),即 \((k-1)P+2 \le g \le (k+1)P\),再由g的上限m我们就得到了k的取值的上下限了,也就是当 \((k+1)P\le m\)时肯定在范围内,当 \((k-1)P+2>m\)时一定无解,所以我们只需要检查上下限之间的k是否满足 \(g\le m\) 即可。也就是check \(\lfloor \frac{m}{P}\rfloor-1 \le k \le \lceil\frac{m-2}{P}\rceil+1\) 内的k是否可行,很显然这部分就几个数,所以时间复杂度是 \(O(1)\)

点击查看代码
#include<bits/stdc++.h>
using namespace std;
using ll=long long;

int main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);
	ll tttt=1;
	cin>>tttt;
	while(tttt--)
	{
		ll p,m;
		cin>>p>>m;
		ll l=max(0ll,m/p-1);
		ll r=max(0ll,(m+p-3)/p+1);
		ll ans=l;
		for(ll i=l;i<=r;i++)
		{
			if(((1+i*p)^(p-1))<=m)ans++;
		}
		cout<<ans<<'\n';
	}
	return 0;
}

G. Knapsack

题意:一个可以免费取 \(k\) 个物品的01背包,物品总数为 \(n\),每个物品的体积和价值为 \(w_i\)\(v_i\) ,能取的总重量为 \(W\)

限制条件:\(1\le n \le 5×10^3,1\le w_i \le W \le 10^4,1 \le v_i \le 10^9,0\le k \le n\)

题解:这题有点歪榜了,我不觉得有那么好想,但是过的人数是第二多的。

需要注意到两点:1.当两个物品我们都要选择时,我们肯定优先免费拿重量更大的物品, 因为这样能够省下来更多的空间,不会更劣。2.当我们在两个物品中间选择哪个要免费拿时,我们肯定优先选择价值更高的, 这样能够最大化收益。

因此可以考虑这样一个策略:把所有的物品按照 \(w_i\) 从小到大排序,然后确定一个分界点,分界点左侧的物品我们只用付费的方法拿,分界点右侧的物品我们只用免费的方法拿。那么左侧就是一个背包dp,我们开个普通的二维数组保留前i个物品背包的解即可;右侧根据上面的第二点,直接取前k个价值最大的物品即可,可以用一个优先队列来维护。时间复杂度 \(O(n(W+logk))\)

点击查看代码
#include<bits/stdc++.h>
using namespace std;
using ll=long long;
struct node
{
	ll w,v;
	friend bool operator < (node a,node b)
	{
		return a.w<b.w;
	}
};
int main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);
	ll tttt=1;
	//cin>>tttt;
	while(tttt--)
	{
		ll n,W,k;
		cin>>n>>W>>k;
		vector<node>c(n+7);
		for(int i=1;i<=n;i++)
		{
			cin>>c[i].w>>c[i].v;
		}
		sort(c.begin()+1,c.begin()+n+1);
		vector<vector<ll>>dp(n+7,vector<ll>(W+7,0));
		for(int i=1;i<=n;i++)
		{
			for(int j=W;j>=0;j--)
			{
				dp[i][j]=dp[i-1][j];
				if(j>=c[i].w)
					dp[i][j]=max(dp[i][j],dp[i-1][j-c[i].w]+c[i].v);
			}
		}
		if(k==0)
		{
			ll tmp=0;
			for(int i=0;i<=W;i++)
			{
				tmp=max(tmp,dp[n][i]);
			}
			cout<<tmp<<'\n';
			continue;
		}
		priority_queue<ll,vector<ll>,greater<ll>>p;
		ll sum=0,ans=0;
		for(int i=n;i>=1;i--)
		{
			ll tmp=0;
			for(int j=0;j<=W;j++)
			{
				tmp=max(tmp,dp[i-1][j]);
			}
			
			if(p.size()<k)
			{
				p.push(c[i].v);
				sum+=c[i].v;
			}
			else if(c[i].v>p.top())
			{
				sum+=c[i].v-p.top();
				p.pop();
				p.push(c[i].v);
			}
			ans=max(ans,tmp+sum);
		}
		cout<<ans<<'\n';
	}
	return 0;
}

A. Cool, It’s Yesterday Four Times More

题意:给定一个 \(n×m\) 的方阵,'.'的位置可以通行并且有一只袋鼠!'O' 的位置是陷阱,碰到就会死!所有的袋鼠一起同时上下左右移动,一只袋鼠碰到陷阱或者走出方阵它就会死!问有哪些位置的袋鼠可能是最后一只活下来的袋鼠,求个数。

限制条件:\(1\le n×m \le 10^3\)

题解:很高兴啊,我能够给出一种不同于题解的做法,倍感荣幸!

我们先考虑暴力的做法,就是枚举每一只袋鼠,然后看它能去的点和它的初始位置的偏差,再和其他所有袋鼠比较,看其中是否至少存在一个偏差使得别的袋鼠去不了,但是它可以。其他每只袋鼠和它相比只要有一个点去不了就行了,不一定要一样。这样的事件复杂度是枚举袋鼠的 \(O(n×m)\),枚举其他所有袋鼠的 \(O(n×m)\),对于每只其他袋鼠枚举它能去的所有点的 \(O(n×m)\),一共是 \(O((n×m)^3)\),会爆的,所以考虑一下怎么进行优化。

首先枚举每一只袋鼠并且枚举其他所有的袋鼠和它比较是节省不了的,我们考虑在比较的部分进行优化,所有的偏差都在(-n,-m)到(n,m)以内,呈现一个矩形。对于这样的一个点对集,我想到了可以用bitset进行优化 ,也就是把所有的可能的偏差按照x从小到大,再按照y从小到大进行排列,然后能到达的偏差记为1。这样比较的时候只要判断可能成为最后一只的袋鼠能到达的偏差的点对集A和要和它比较的袋鼠能到达的偏差的点对集B的差集A-B是否不为空就可以了!如果不为空就说明有某个点是在A内但是不在B内,那么只要走到这个偏差就可以把后者的袋鼠杀掉。

如上,我们总的时间复杂度就降低为了 \(O(\frac{(n×m)^3}{\omega})\),粗略计算一下就是 \(10^3×10^3×10^3/32=3.125×10^7\),看起来可以通过!实际上也正是如此,QOJ跑了465ms,绰绰有余地通过了!虽然速度比不上正解的 \(O((n×m)^2)\),但是我觉得这是非常自然好理解的思路,只要通过了就是好的方法!

附:在这里讲解一下排列每个偏差的部分。首先我们先给每个偏差加上(n,m),这样偏差的范围就变成了(0,0)到(2n,2m)之间,然后每个(x,y)我们直接对应一个数 \(2mx+y\) 就可以和原来的点对形成双射了。但是由于bitset不能开变量的长度,所以我们保守起见大小得开4(n×m)的上限也就是4000以上。

点击查看代码
#include<bits/stdc++.h>
using namespace std;
using ll=long long;
ll dx[4]={-1,1,0,0};
ll dy[4]={0,0,-1,1};
int main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);
	ll tttt=1;
	cin>>tttt;
	while(tttt--)
	{
		ll n,m;
		cin>>n>>m;
		vector<pair<ll,ll>>p;
		vector<vector<char>>s(n+7,vector<char>(m+7));
		for(int i=1;i<=n;i++)
		{
			for(int j=1;j<=m;j++)
			{
				cin>>s[i][j];
				if(s[i][j]=='.')
					p.push_back({i,j});
			}
		}
		vector<bitset<4007>>yw;
		for(auto [i,j]:p)
		{
			queue<pair<ll,ll>>q;
			bitset<4007>b;
			vector<vector<bool>>vis(n+7,vector<bool>(m+7,0));
			q.push({i,j});
			vis[i][j]=1;
			while(!q.empty())
			{
				auto [x,y]=q.front();
				b[(x+n-i)*2*m+(y+m-j)]=1;
				q.pop();
				for(int _=0;_<4;_++)
				{
					ll xx=x+dx[_],yy=y+dy[_];
					if(xx<=0||xx>n||yy<=0||yy>m)continue;
					if(s[xx][yy]=='O'||vis[xx][yy])continue;
					q.push({xx,yy});
					vis[xx][yy]=1;
				}
			}
			yw.push_back(b);
		}
		// for(int i=0;i<yw.size();i++)
		// {
			// cout<<yw[i]<<'\n';
		// }
		ll ans=0;
		for(int i=0;i<p.size();i++)
		{
			auto [x,y]=p[i];
			bool ok=1;
			for(ll j=0;j<p.size();j++)
			{
				if(i==j)continue;
				if((yw[i]&(~yw[j]))==0)
				{
					ok=0;
					break;
				}
			}
			if(ok)
			{
				ans++;
				//cout<<x<<' '<<y<<'\n';
			}
		}
		cout<<ans<<'\n';
	}
	return 0;
}

表现非常好的一场!后面的题就等我长大后再来补吧~

posted @ 2025-11-09 14:02  外地  阅读(20)  评论(0)    收藏  举报