2024 ICPC National Invitational Collegiate Programming Contest, Wuhan Site解题记录


I,K,B,E都比较水,就不写了

F:Custom-Made Clothes:

交互题,直接求解第k大有难度,由于对于任意一个点(x,y),只能确定左下与右上部分的数与它的大小关系,对于左上与右下部分的数,无法确定其大小关系,这时扭转思路,想到可以试验数val是否为第k大,由此想到二分答案,并通过单调关系验证其为第几大,从而得出答案。


D:ICPC

dp题,难点在于状态转移过程。
性质1:最优的走法一定可以是只走一个方向或只转一次方向
考虑设计dp状态,不难设计:$$dp[i][j]为起始于第i个点,走j步$$ 考虑转移过程:

  1. 首先考虑只走一个方向,可以用前缀和快速计算出只走一个方向(往左或往右)的最大值
  2. 考虑只转一个方向:对于第i个点,由于其有先向右走再往左走或先往左走再往右走两种可能,故其需要i-1点,i+1点的状态来转移,这显然是无法设计转移顺序的。因此,我们考虑先考虑先左后右的情况,再考虑先右再左的情况,将两种情况分开,确定方向,则可以设计转移顺序。
  3. 对于先左后右的情况:\(dp[i][j]基于dp[i - 1][j - 1],因为无论最优情况是向左多少步,其一定会经过i -1,且最优情况一定会被i - 1的最优情况包含(因为先左后右的情况下必定会回到并重新经过i,其实相当于多走了一步重复的)\)
  4. 对于先右后左的情况:同理
    初始状态对,转移方法对,且转移顺序可设计且正确,则dp正确。
    code:
#include <bits/stdc++.h>
using namespace std;
const int N = 5010;
long long a[N],dp[N][N << 1];
long long pre[N];
long long sg[N][N << 1];
int n;
int main()
{
	ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
	cin >> n;
	for(int i = 1;i <= n;i++)
	{
		cin >> a[i];
		pre[i] = pre[i - 1] + a[i];
	}
	int l,r;
	for(int i = 1;i <= n;i++)
	{
		for(int j = 0;j <= (n << 1);j++)
		{
			l = max(i - j ,1);
			r = min(i + j,n);
			sg[i][j] = max(pre[i] - pre[l - 1],pre[r] - pre[i - 1]);
		}
	}
	for(int i = 1;i <= n;i++)
	{
		for(int j = 1;j <= (n << 1);j++)
		{
			dp[i][j] = max(sg[i][j],dp[i - 1][j - 1]);
		}
	}
	for(int i = n;i >= 1;i--)
	{
		for(int j = 1;j <= (n << 1);j++)
		{
			sg[i][j] = max(sg[i][j],sg[i + 1][j - 1]);
			dp[i][j] = max(sg[i][j],max(dp[i][j],dp[i + 1][j - 1]));
		}
	}
	long long ans = 0;
	for(int i = 1;i <= n;i++)
	{
		long long temp = 0;
		for(int j = 1;j <= (n << 1);j++)
			temp ^= (j * dp[i][j]);
		temp += i;	
		ans ^= temp;
	}
	cout << ans << endl;
	return 0;
}

C:TreeBag and LIS

构造题就是找到一种合理的自限方式(或多种),使结果可控。
十分神奇的构造题,首先这种要凑 和恰好为某一特定数的题目,优先想到exgcd(走完凑和的最后一公里路)。
接下来考虑如何用\(10^5\)个数字来凑\(10^{13}\) 的范围。首先,既然题目要找LIS,那一种非常合理的思路就是先自限LIS的长度,易得LIS的长度为1和2时都无法达到\(10^{13}\) 的范围,则考虑LIS的长度为3的情况。
我们希望将LIS分别限制再一个区间内,这样可以保证LIS间互不干扰,不会出现前面LIS中的数跟后面的LIS中的数产生新LIS,那么一种办法就是将大的数放前面,小的放后面,这样前面大的数就在后面区间外找不到更大的数来构成LIS.即:$$ 7...78....89...9|5...56...67....7|3....34....45...5|0.....0230....012 $$
明显发现最后一段0123数字构成的序列与前面有不一样的规律,其原因是前面都是为了快速将和变大,使其能逼近\(10^{13}\)的范围,但最后一段就要确保和恰好为需要的数了,这时候再用前面的方法无法控制,那么我们考虑利用前置0来用0的个数来控制23与12的个数,但因前面的0也可与后面的12构成LIS,故实际上是由35与12来凑最后的和来确保和恰好为特定数,此处使用exgcd即可。需要注意的是,当剩下的和大于等于35 * 12时才能保证一定有非负整数解(可行解),故要提前预留35*12的余量。若x < 35 * 12,直接纯1构造即可。
code:

#include <bits/stdc++.h>
#define int long long
using namespace std;
long long n,gg;
const int f[10] = {0,789ll,567ll,345ll,23ll,12ll};
int tim[10][10];
int gcd(int a, int b) {
    while (b != 0) {
        int t = b;
        b = a % b;
        a = t;
    }
    return a;
}
void exgcd(int a, int b, int &x, int &y) {
    if (b == 0) {
        x = 1ll;
        y = 0ll;
    } else {
        exgcd(b, a % b, y, x);
        y -= a / b * x;
    }
}
pair<int, int> solve(int a, int b, int c) {
    int x, y;
    exgcd(a, b, x, y);
    x *= c; y *= c;
    int d = gcd(a, b); // 最大公约数
    int k = 0ll;
    while (true) {
        int a_val = x + b * k;
        int b_val = y - a * k;
        if (a_val > 0 && b_val > 0) {
        	int tt = y / (a * k);
        	b_val = y - (a * k) * tt;
        	a_val = x + (b * k) * tt;
            return make_pair(a_val, b_val);
        }
        k++;
    }
}

int check(pair <int,int> pp)
{
	int tsum = 0;
	for(int k = 1;k <= 2;k++)
	{
		int te = 1;
		for(int i = 1;i <= 3;i++)
			te *= tim[k][i];
		tsum += f[k] * te;
	}
	tsum += pp.first * 35 + pp.second * 12;
	if(tsum == gg)
	{
		cout << "Accepted" << endl;
		return 1;
	}
	else 
	{
		cout << "fuck" << endl;
		cout << tsum << " " << gg << " " << gg - tsum << endl;
		return 0;
	}
}
signed main()
{
	ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
	//freopen("in.txt","w",stdout);
	cin >> n;
	gg = n;
	if(n > 35ll * 12ll + 1)
	{
		n -= (35ll * 12ll + 1);
		for(int i = 1;i <= 3;i++)
		{
			int temp = f[i];
			if(temp > n)continue;
			tim[i][1] = tim[i][2] = tim[i][3] = 1; 
			int ntim = 1;
			int wtim = 0;
			for(int cnto = 0;;cnto = (cnto + 1) % 3)
			{
				int cnt = cnto + 1;
				//cout << "cnt:" << cnt << endl;
				if(wtim > 3)break; 
				ntim = ntim * (tim[i][cnt] + 1)/ tim[i][cnt] ;
				if(ntim * temp <= n)
				{
					tim[i][cnt]++;
					wtim = 0;
					continue;
				}
				else 
				{
					ntim = ntim * tim[i][cnt]/ (tim[i][cnt] + 1) ;
					wtim++;
					continue;
				}
			}
			n -= ntim * temp;
		}
		n += 35 * 12 + 1;
		pair <int,int> pa = solve(35,12,n);
		
		//if(n == pa.first * 35 + pa.second * 12)cout <<"exgcd accepted" << endl;
		//else cout << "exgcd wrong:" << n << endl;
		//int flag = check(pa);
		//if(!flag)
		//{
			//return 0;
		//}
		for(int i = 1;i <= tim[1][1];i++)
			cout << 7;
		for(int i = 1;i <= tim[1][2];i++)
			cout << 8;
		for(int i = 1;i <= tim[1][3];i++)
			cout << 9;
		for(int i = 1;i <= tim[2][1];i++)
			cout << 5;
		for(int i = 1;i <= tim[2][2];i++)
			cout << 6;
		for(int i = 1;i <= tim[2][3];i++)
			cout << 7;
		for(int i = 1;i <= tim[3][1];i++)
			cout << 3;
		for(int i = 1;i <= tim[3][2];i++)
			cout << 4;
		for(int i = 1;i <= tim[3][3];i++)
			cout << 5;
		//cout << pa.first * 35 + pa.second * 12 << endl;
		if(pa.first != 0)
		{
			for(int i = 1;i <= pa.first;i++)
				cout << 0;
			cout << 23;
		}
		if(pa.second != 0)
		{
			for(int i = 1;i <= pa.second;i++)
				cout << 0;
			cout << 12;
		}
	}
	else
	{
		if(n == 0)
		{
			cout << 0;
		}
		else
		{
			for(long long i = 1;i <= n;i++)
			{
				cout << 1;
			}
		}
	}
	return 0;
}

G:Pack

除法分块,难点在于将题意转化为数学式子,用一个简洁恰当的数学式子表达题意,根据题意,首先,需要满足装的方案和恰好为k,如此不难想到使用exgcd构造一组解(这场比赛咋这么爱exgcd),设这组解为\(k=a*x+b*y\) ,那么,显然,所有可能解都可以表示为\((x+k*Δx,y+k*Δy)\) ,那么对于每一组 \((x_i,y_i)\),不难得到其最多装\(min(n/x_i,m/y_i)\) 组,则最后剩下\(n+m-(x_i+y_i)*min(n/x_i,m/y_i)\)件物品。那么对于枚举所有的\((x_i,y_i)\)显然不可行,注意到n,m的范围也仅有\(10^9\) ,又有\(n/x_i,m/y_i\)存在,不难想到除法分块将需要枚举的个数限制在\(\sqrt{n} + \sqrt{m}\) 次内,对于同一个分块内,即对于每一段\(n/x_i,m/y_i\)不变的\((x_i,y_i)\),直接计算\(x_i+y_i\)的最大值即可,因为组数不变,每组装得越多剩下得越少。

#include <bits/stdc++.h>
#define int long long
using namespace std;
int n,m,val1,val2;
int k;
int T;
int d1,d2,n1,n2;
int exgcd(int a, int b, int& x, int& y) {
    if (b == 0) 
    {
        x = 1, y = 0;
        return a;
    }
    int g = exgcd(b, a % b, y, x);
    y -= a / b * x;
    return g;
}
pair <int,int> solve(int A,int B,int C) {
    int x0, y0;
    int g = exgcd(A, B, x0, y0);//a,b的最大公约数
    if (C % g != 0)
    {
        cout << "-1\n";
    }
    int a = A / g, b = B / g, c = C / g;
    x0 *= c;y0 *= c;//这一步求出来的x0,y0才是ax+by=c的一个解
	/*int rx=x0%b,ry=y0%a;
	if(rx<=0)
		rx+=b;
	if(ry<=0)
		ry+=a;*/
	b = abs(b);
	a = abs(a);
	d1 = b;
	d2 = a;
	if(x0 < 0)
	{
		int tt = (0 - x0) / b;
		x0 += tt * b;
		y0 -= tt * a;
	}
	if(y0 < 0)
	{
		int tt = (0 - y0) / a;
		x0 -= tt * b;
		y0 += tt * a;
	}
	while(1)
	{
		if(x0 >= 0 && y0 >= 0)break;
		if(x0 < 0)
		{
			x0 += b;
			y0 -= a;	
		}	
		else 
		{
			x0 -= b;
			y0 += a;
		}
	}
	int tt = x0 / b;
	x0 -= tt * b;
	y0 += tt * a;
	return make_pair(x0,y0);
}	
int now1,now2,nxt1,nxt2;
int flag = 1;
void init()
{
	int temp;
	now1 = n1;
	if(now1 == 0)
	{
		nxt1 = 1;
	}
	else
	{
		temp = n / now1;
		nxt1 = n / temp + 1;
	}
	now2 = n2;
	temp = m / now2;
	nxt2 = m / (temp + 1);	
}
void sol()
{
	if(now1 > n || now2 < 1)
	{
		flag = 0;
		return;
	}
	//cout << "begin" << endl;
	int temp;
	int g1 = nxt1 - now1;
	int g2 = nxt2 - now2;
	g2 = abs(g2);
	int b1 = g1 % d1 == 0 ? g1 / d1 : g1 / d1 + 1;
	int b2 = g2 % d2 == 0 ? g2 / d2 : g2 / d2 + 1;
	//cout << b1 << " " << d1 << " " << b2 << " " << d2 << endl;
	if(b1 <= b2)
	{
		now1 += b1 * d1;
		now2 -= b1 * d2;
		if(now1 > n || now2 < 1)
	{
		flag = 0;
		return;
	}
		if(now1 == 0)
		{
			nxt1 = 1;
		}
		else
		{
			temp = n / now1;
			nxt1 = n / temp + 1;
		}
		if(now2 == 0)
		{
			flag = 0;
			return;
		}
		temp = m / now2;
		nxt2 = m / (temp + 1);
	}
	else
	{
		now1 += b2 * d1;
		now2 -= b2 * d2;
			if(now1 > n || now2 < 1)
	{
		flag = 0;
		return;
	}
		if(now1 == 0)
		{
			nxt1 = 0;
		}
		else
		{
			temp = n / now1;
			nxt1 = n / temp + 1;
		}
		if(now2 == 0)
		{
			flag = 0;
			return;
		}
		temp = m / now2;
		nxt2 = m / (temp + 1);
	}//cout << "end" << endl;
	if(now1 > n || now2 < 1)
	{
		flag = 0;
		return;
	}
	
}
signed main()
{
	ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
	cin >> T;
	while(T--)
	{
		now1 = now2 = nxt1 = nxt2 = 0;
		flag = 1;
		d1 = d2 = n1 = n2 = 0;
		cin >> n >> m >> val1 >> val2;
		cin >> k;
		if(val1 < val2)
		{
			swap(val1,val2);
			swap(n,m);
		}
		//cout << val1 << " " << val2 << endl;
		pair <int ,int> pp = solve(val1,val2,k);
		n1 = pp.first;//开始极小 
		n2 = pp.second; /// 开始极大 
		
		init();//cout << now1 << " " << now2 << endl;
		int ans = 0;
		while((now1 == 0 ||(now1 * now2 > 0 && n / now1 >= m / now2 ))&& flag)
		{
			
			ans = max(ans,(now1 + now2) * (m / now2));
			//cout << ans << " " << now1 << " " << now2 << endl ;
			sol();
		}
		if(now2 != 0)
		{
			while(flag && (n / now1 < m / now2 && now1 * now2 > 0) )
			{
				//cout << "session2 :"<< now1 << " " << now2 << endl;
				ans = max(ans,(now1 + now2) * (n / now1));
				//cout << ans << " " << now1 << " " << now2 << endl ;
				sol();
			}
		}
		if(k % val1 == 0)
		{
			int tt = k / val1;
			int g = n % tt;
			ans = max(ans,n - g);
		}
		cout << n + m - ans << endl; 
	}
	return 0;
}

M:Merge

注意到合并是x + (x + 1),不难得到每一次合并出来的和都是奇数,不可能合出偶数,那也就意味着每一次合并都需要一个原序列中的偶数,不难想到将原序列的偶数从大到小排序,依次判断是否能凑出使该偶数发生合并的奇数,再对原序列数是否使用进行一些维护即可。

posted @ 2024-10-11 20:00  kanade16  阅读(16)  评论(0编辑  收藏  举报