2021寒假刷题记录(部分)

1 基础算法

1.1 Doing Homework again(贪心)

Problem Description
Ignatius has just come back school from the 30th ACM/ICPC. Now he has a lot of homework to do. Every teacher gives him a deadline of handing in the homework. If Ignatius hands in the homework after the deadline, the teacher will reduce his score of the final test. And now we assume that doing everyone homework always takes one day. So Ignatius wants you to help him to arrange the order of doing homework to minimize the reduced score.

Input
The input contains several test cases. The first line of the input is a single integer T that is the number of test cases. T test cases follow.
Each test case start with a positive integer N(1<=N<=1000) which indicate the number of homework… Then 2 lines follow. The first line contains N integers that indicate the deadlines of the subjects, and the next line contains N integers that indicate the reduced scores.

Output
For each test case, you should output the smallest total reduced score, one line per test case.

题目连接

按照分数排序 + 贪心

#include<bits/stdc++.h>
using namespace std;
//9 : 19660813 and 19260817
//18: 154590409516822759 and 212370440130137957
//19: 2305843009213693951(ll max > 9e19)
//std::ios::sync_with_stdio(false);
//int a[N] Nmax = 4e8;
#define Min(a,b,c)  min(a,min(b,c))
#define Max(a,b,c)  max(a,max(b,c))
#define fi first
#define se second

typedef long long ll;
typedef unsigned long long ull;
const int INF = 0x3f3f3f3f;
const double pi = 3.1415926535898;
const double eps = 1e-8;

struct node{
	int dead;
	int score;
	bool operator < (const node &b) const
	{
		return this->score > b.score;
	}
};
node a[1010];
int v[1010];

int main()
{
	ios::sync_with_stdio(false);
	int t;
	cin >> t;
	while(t--)
	{
	    int n;
	    cin >> n;
	    for (int i = 1; i <= n; i++)
	    	cin >> a[i].dead;
	    for (int i = 1; i <= n; i++)
	        cin >> a[i].score;
	    sort(a+1, a+n+1);
	    memset(v, 0, sizeof(v));
	    int ans = 0;
	    for (int i = 1; i <= n; i++)
	    {
	    	int flag = 0;
	    	for (int j = a[i].dead; j >= 1; j--)
	    	{
	    		if (!v[j])
	    		{
	    			flag = 1;
	    			v[j] = 1;
	    			break;
				}
			}
			if (!flag)
			    ans += a[i].score;
		}
		cout << ans << endl;
	}
	return 0;
}

1.2 魏迟燕的自走棋 (贪心)

在这里插入图片描述
分析:
此题我一开始我把人和装备分别作为两个点集,可供使用关系为边,建立二分图,但是由于本人对二分图不够熟悉,觉得在二分图上处理最大带权匹配会超时,但是好像也有(类似)这么做AC了的代码,这里先不探究,下面是正解(官方题解)。

  1. 从图的角度思考
    在这里插入图片描述

  2. 直接从实际意义出发
    在这里插入图片描述

下面贴代码

#include<bits/stdc++.h>
using namespace std;

const int N = 100010;
typedef long long ll;

int n, m;
int fa[N], v[N];

struct node{
	int x, y, z;
	bool operator < (const node &b) const
	{
		return this->z < b.z;
	}
}; 
node a[N];

int get(int x)
{
	int r = x;
	while (r != fa[r])
		r = fa[r];
	int t;
	while (fa[x] != r)
	    t = fa[x], fa[x] = r, x = t; 
	return r;   
}

void merge(int x, int y)
{
	int rx = get(x), ry = get(y);
	if (rx != ry)
	    fa[rx] = ry;
}

int main()
{
	cin >> n >> m;
	for (int i = 1; i <= n; i++)
	    fa[i] = i;
	ll ans = 0;
	for (int i = 1; i <= m; i++)
	{
		int k;
		cin >> k >> a[i].x;
		if (k & 1)
		    a[i].y = a[i].x;
		else
		    cin >> a[i].y;
	    cin >> a[i].z;
	}
	sort(a + 1, a + m + 1);
	for (int i = m; i >= 1; i--)
	{
        int x = a[i].x, y = a[i].y, z = a[i].z;
		int rx = get(x), ry = get(y);
		if (rx == ry && !v[ry])
		{
			ans += z;
			v[ry] = 1;
		}
		else if (!v[rx] || !v[ry])
		{
			merge(x, y);
			ans += z;
			if (v[rx] || v[ry])
			    v[get(x)] = 1;
		}
	}
	cout << ans << endl;
	return 0;
}

1.3 内卷(尺取法)

题意:在5行n列的矩阵中,每列取且仅取一个数,并且第 1 行不能取超过 k 个数,使得它们之中的 Max - Min 最小。
在这里插入图片描述
题目链接

隐约记得之前cf做了一题和这道差不多,但是实在是找不到了。

分析:
可以考虑这样做:对于每一个数,用三元组(val,id,pos)记录分数、第几个人的、等级。将这5*n个元素按照val从小到大排序。目标即转化为了找一段区间[l, r],这个区间元素包含n个不同的id,并且只有pos = 1的 id 不超过k个,r.val - l.val最小。
如何求这个区间?暴力(for (i = 1; i <= 5 * n; i++) for(j = i+1; j <= n; j++))?显然超时。
考虑到两点:

  1. 对于每个i,找到相应的j后,j就不需要继续增大。
  2. 当 i 变成 i + 1时,j 不需要从i + 1开始循环,只需要接着上次的递增。

即可用尺取法解决问题。

#include<bits/stdc++.h>
using namespace std;

const int INF = 0x3f3f3f3f;

const int N = 100000 + 10;

int n, k;
int v[N], is[N];
int diff, only;

struct node{
	int val, id, pos;
	bool operator < (const node &b) const
	{
		return this->val < b.val;
	}
};
node a[5*N];

int check()
{
	if (diff < n) return 0;
	
	if (only > k) return 0;
	
	return 1;
}

int main()
{
	ios::sync_with_stdio(false);
	cin >> n >> k;
	for (int i = 1; i <= n; i++)
	    for (int j = 1; j <= 5; j++)
	    {
	        cin >> a[(i-1)*5+j].val;
	        a[(i-1)*5+j].id = i;
	        a[(i-1)*5+j].pos = j;
	    }
	    
	sort(a+1, a+5*n+1);
    
	int ans = INF;
    diff = only = 0;
	for (int l = 1, r = 0; l <= 5*n;)
	{
		while (!check() && r < 5*n) //r向右试探
		{
			r++;
			if(!v[a[r].id])
			{
				diff++;
				if (a[r].pos == 1)
				{
					is[a[r].id] = 1;
				    only++;
				}
			}
			else
			{
				if (a[r].pos == 1)
				    is[a[r].id] = 1;
				else
				{
					if (v[a[r].id] == 1 && is[a[r].id])
					    only--;
				}
		    }
			v[a[r].id]++;			
		}
		
		if (!check()) break; //r已经到了最右侧,但是还是没有满足,直接结束

	
		ans = min(ans, a[r].val - a[l].val); //更新答案
		
		//l向左移动一个
		if (v[a[l].id] == 1) 
		{
			diff--;
			if (a[l].pos == 1)
			{
				only--;
				is[a[l].id] = 0;
			}
		}
		else if (v[a[l].id] == 2)
		{
			if (a[l].pos == 1)
			{
				is[a[l].id] = 0;
			}
			else
			{
				if (is[a[l].id])
					only++;
			}
		}
		else
		{
			if (a[l].pos == 1)
			    is[a[l].id] = 0;
		}
		v[a[l].id]--;
		
		l++;
	}
	    
	cout << ans << endl;    
	    
	return 0;
}

1.4 小宝的幸运数组(思维)

题目描述:
对于小宝来说,如果一个数组的总和能够整除他的幸运数字k,就是他的幸运数组,而其他数组小宝都很讨厌。现在有一个长度为n的数组,小宝想知道这个数组的子数组中,最长的幸运子数组有多长。

对于子数组的定义,如果可以通过从开头和从结束分别删除若干个(可以为零或全部,前后删除个数不必相同)元素来从数组b获得数组a,则称数组a是数组b的子数组。(子数组包含原数组,但不包含空串)

输入描述:
多组输入。第一行包含一个整数T(1≤T≤10),表示有T组测试数据。

每组测试数据包含两行,第一行包含两个整数n和k(1≤n≤105,1≤k≤105),分别表示数组长度和小宝的幸运数字。第二行包含n个空格分隔的整数a1,a2,.,an(0≤ai≤10^9),为数组的元素。

输出描述:
对于每组数据,输出和能被k整除的最长子数组的长度。如果没有这样的子数组,则输出−1。

题目链接

题目大意:求序列能整除k的最长字串和的长度。

sum[i]是前缀和,要求所有 (sum[r] - sum[l-1]) % k == 0 (1 <= l <= r <= n) 中 r-l+1 的长度。很容易想到枚举O(n^2)算法,但是会超时的。
于是对式子变形,得到sum[l-1] % k == sum[r] %k,所以可以先对前缀和数组取余,记录取余后值相等的点在最左边和最右边的点出现的位置即可O(n)求出。

#include<bits/stdc++.h>
using namespace std;
#define Min(a,b,c)  min(a,min(b,c))
#define Max(a,b,c)  max(a,max(b,c))
#define fi first
#define se second

typedef long long ll;
typedef unsigned long long ull;
const int INF = 0x3f3f3f3f;
const double pi = 3.1415926535898;
const double eps = 1e-8;

int n, k;
int sum[100010];
int r[100010][3];

int main()
{
	ios::sync_with_stdio(false);
	int t;
	cin >> t;
	while(t--)
	{
		cin >> n >> k;
		memset(r, -1, sizeof(r));
		r[0][1] = 0;
		sum[0] = 0;
		for (int i = 1; i <= n; i++)
		{
			int x;
			cin >> x;
			sum[i] = (sum[i-1] + x) % k;
			if(r[sum[i]][1]==-1)
				r[sum[i]][1] = i;
			else
			    r[sum[i]][2] = i;
			    //cout << x << endl;
		}
		int ans = -1;
		for(int i = 0; i < k; i++)
		{
			if(r[i][1]!=-1 && r[i][2] != -1)
			    ans = max(ans, r[i][2] - r[i][1]);
		}
		cout << ans << endl;
	}
	
	return 0;
}

1.5 Max Median(中位数、二分)

题意:求给定的序列,所有长度不小于k的子数组的中位数最大值。
在这里插入图片描述
分析:
考虑答案是否大于等于x,那么对于原数组,大于等于x的赋值为1,小于x的赋值为-1。如果找到了一个区间和大于0,就说明最终的答案是大于等于x的,可以通过前缀和O(n)计算 [ i从k扫描到n,每次sum[i] - (1~i-k的最小前缀和) ]。
x的取值只需要对1~n二分即可。

#include<bits/stdc++.h>
using namespace std;

const int INF = 0x3f3f3f3f;

int n, k;
int a[200020];
int sum[200020];

int check(int x)
{
	for (int i = 1; i <= n; i++)
	    sum[i] = sum[i - 1] + (a[i] >= x ? 1 : -1);
	int minn = INF;
	for (int i = k; i <= n; i++)
	{
		minn = min(minn, sum[i - k]);
		if (sum[i] - minn > 0)
		    return 1;
	}
	return 0;
}

int main()
{
	cin >> n >> k;
	for (int i = 1; i <= n; ++i)
	    cin >> a[i];
	int l = 1, r = n;
	while(l < r)
	{
		int mid = l + r + 1 >> 1;
		if (check(mid))
		    l = mid;
		else
		    r = mid - 1;
	}
	cout << l << endl;
	return 0;
}

2 DP

2.1 Doing Homework(状压DP)

Ignatius has just come back school from the 30th ACM/ICPC. Now he has a lot of homework to do. Every teacher gives him a deadline of handing in the homework. If Ignatius hands in the homework after the deadline, the teacher will reduce his score of the final test, 1 day for 1 point. And as you know, doing homework always takes a long time. So Ignatius wants you to help him to arrange the order of doing homework to minimize the reduced score.
Input
The input contains several test cases. The first line of the input is a single integer T which is the number of test cases. T test cases follow.
Each test case start with a positive integer N(1<=N<=15) which indicate the number of homework. Then N lines follow. Each line contains a string S(the subject’s name, each string will at most has 100 characters) and two integers D(the deadline of the subject), C(how many days will it take Ignatius to finish this subject’s homework).

Note: All the subject names are given in the alphabet increasing order. So you may process the problem much easier.
Output
For each test case, you should output the smallest total reduced score, then give out the order of the subjects, one subject in a line. If there are more than one orders, you should output the alphabet smallest one.

状压DP:

#include<bits/stdc++.h>
using namespace std;
//9 : 19660813 and 19260817
//18: 154590409516822759 and 212370440130137957
//19: 2305843009213693951(ll max > 9e19)
//std::ios::sync_with_stdio(false);
//int a[N] Nmax = 4e8;
#define Min(a,b,c)  min(a,min(b,c))
#define Max(a,b,c)  max(a,max(b,c))
#define fi first
#define se second

typedef long long ll;
typedef unsigned long long ull;
const int INF = 0x3f3f3f3f;
const double pi = 3.1415926535898;
const double eps = 1e-8;

int n;
struct node{
	string name;
	int end;
	int take;
};
node a[20];

struct status{
	int time;
	int result;
	int path;
};
status s[1<<15]; 

int main()
{
	ios::sync_with_stdio(false);
	int t;
	cin >> t;
	while(t--)
	{
	    cin >> n;
	    for (int i = 1; i <= n; i++)
		    cin >> a[i].name >> a[i].end >> a[i].take;		
	    s[0].time = s[0].result = s[0].path = 0;
    	for (int i = 1; i <= (1 << n) - 1; i++)
	    {
		    s[i].result = INF;
    		for (int k = 0; k < n; k++)
	    	{
		    	if (i & (1 << k))
		        {
		        	int ago = i - (1 << k);
		        	int time = s[ago].time + a[k+1].take;
		        	int result = s[ago].result + max(0, time - a[k+1].end);		        	
		    	    if (result <= s[i].result)
		    	    {
		    		    s[i].result = result;
		    		    s[i].time = time;
		    		    s[i].path = k;
			    	}
			    }
		    }
	    }
	    cout << s[(1 << n) - 1].result << endl;
	    stack<int> sta;
    	int p = (1 << n) - 1;
    	while(p)
    	{
	    	sta.push(s[p].path + 1);
		    p = p - (1 << s[p].path);
	    }
    	while(sta.size())
	    {
		    cout << a[sta.top()].name << endl;
		    sta.pop();
	    }
	}
	return 0;
}

2.2 蒙德里安的梦想(状压DP)

在这里插入图片描述
经典题。在N = 2时可以通过线性DP简单求得。

这道题要用到数位DP,对于每一行,用一个M位二进制数,其中第k(0<=k<m)位为1表示第k列是一个竖着的1*2长方形的上面一半,第k位为0表示其他情况。
dp[i][j]表示第 i 行的形态为 j 时,前 i 行分割方案总数。
------《算法竞赛进阶指南》

#include<bits/stdc++.h>
using namespace std;

typedef long long ll;
const int INF = 0x3f3f3f3f;

int n, m;
ll dp[12][1 << 11];
int v[1 << 11];

int main()
{
	ios::sync_with_stdio(false);
	while (cin >> n >> m, n || m)
	{
		for (int i = 0; i < 1 << m; i++)
		{
			int cnt = 0;
			int flag = 1;
			for (int j = 0; j < m; j++)
			{
				if ((i >> j) & 1)
				{
					if (cnt & 1)
					{
						flag = 0;
						break;
					}
					cnt = 0;
				}
				else
				    cnt++;
			}
			if (cnt & 1)
			    flag = 0;
		    v[i] = flag;
		    
		}
		dp[0][0] = 1;
		for (int i = 1; i <= n; i++)
		{
			for (int j = 0; j < 1 << m; j++)
			{
				dp[i][j] = 0;
				for (int k = 0; k < 1 << m; k++)
				{
					if ((j & k) == 0 && v[j | k])
					    dp[i][j] += dp[i-1][k];
				}
			}
		}
		cout << dp[n][0] << endl;
	}
	return 0;
}

时间复杂度:O(2M2MN) = O(4MN)。

3 数据结构

3.1 DQUERY - D-query(树状数组)

在这里插入图片描述
题目链接

用莫队、线段树、树状数组都可以解决。先写个树状数组解法。

参考博客:

https://blog.csdn.net/riba2534/article/details/81128660

#include<bits/stdc++.h>
using namespace std;
#define Min(a,b,c)  min(a,min(b,c))
#define Max(a,b,c)  max(a,max(b,c))
#define fi first
#define se second

typedef long long ll;
typedef unsigned long long ull;
const int INF = 0x3f3f3f3f;
const double pi = 3.1415926535898;
const double eps = 1e-8;

int n, m;
int a[30000];
int c[30000];
struct node{
	int l, r, id;
	bool operator < (const node &b) const
	{
		return this->r < b.r;
	}
};
node q[200020];
int ans[200020];

int ask(int x)
{
	int ans = 0;
	for (; x; x -= x & -x)
	    ans += c[x];
	return ans;
}

void add(int x, int y)
{
	for (; x <= n; x += x & -x)
	    c[x] += y;
}

int main()
{
	ios::sync_with_stdio(false);
	cin >> n;
	for (int i = 1; i <= n; i++)
	    cin >> a[i];
	cin >> m;
	for (int i = 1; i <= m; i++)
	{
	    cin >> q[i].l >> q[i].r;
	    q[i].id = i;
	}
	sort(q+1, q+m+1);
	map<int, int> mp;
	int cur = 1;
	for (int i = 1; i <= n; i++)
	{
	    if (mp.find(a[i]) != mp.end())
	        add(mp[a[i]], -1);
	    add(i, 1);
	    mp[a[i]] = i;
	    while (cur <= m && i == q[cur].r)
	    {
	    	ans[q[cur].id] = ask(q[cur].r) - ask(q[cur].l - 1);
	    	cur++;
		}
	}
	for (int i = 1; i <= m; i++)
	    cout << ans[i] << endl;
	return 0;
}

3.2 Mishka and Interesting sum(思维,树状数组)

在这里插入图片描述
题目链接
抛开思维部分,主体算法就是要求[L, R]区间内不同数的异或和。做法与上题DQUERY - D-query一样,离线用树状数组解决。
其实 异或 和 加法 在一些情况下还是有很相似的性质的。

#include<bits/stdc++.h>
using namespace std;
//9 : 19660813 and 19260817
//18: 154590409516822759 and 212370440130137957
//19: 2305843009213693951(ll max > 9e19)
//std::ios::sync_with_stdio(false);
//int a[N] Nmax = 4e8;
#define Min(a,b,c)  min(a,min(b,c))
#define Max(a,b,c)  max(a,max(b,c))
#define fi first
#define se second

typedef long long ll;
typedef unsigned long long ull;
const int INF = 0x3f3f3f3f;
const double pi = 3.1415926535898;
const double eps = 1e-8;

ll n;
ll a[1000010];
ll c[1000010];
ll f[1000010];
ll ans[1000010];
map<ll, ll> mp;

struct node{
	ll l, r, id;
	bool operator < (node b)
	{
		return this->r < b.r;
	}
};
node q[1000010];

ll ask(ll x)
{
	ll ans = 0;
	for (; x; x -= x & -x)
	    ans ^= c[x];
	return ans;
}

ll add(ll x, ll y)
{
	for(; x <= n; x += x & -x)
	    c[x] ^= y;
}

int main()
{
	
	scanf("%lld", &n);
	for (ll i = 1; i <= n; i++)
	{
		scanf("%lld", &a[i]);
		f[i] = f[i-1] ^ a[i];
	} 
	ll m;
	scanf("%lld", &m);
	for (ll i = 1; i <= m; i++)
	{
		scanf("%lld%lld",&q[i].l, &q[i].r);
		q[i].id = i;
	}
	sort(q+1, q+m+1);
	
	ll cur = 1;
	for (ll i = 1; i <= m; i++)
	{
		for (ll j = cur; j <= q[i].r; j++)
		{
			if (mp.find(a[j]) != mp.end())
			    add(mp[a[j]], a[j]);
			add(j, a[j]);
			mp[a[j]] = j;
		}
		ans[q[i].id] = f[q[i].r] ^ f[q[i].l - 1] ^ ask(q[i].r) ^ ask(q[i].l - 1);
		cur = q[i].r + 1;
	}
	
	for (ll i = 1; i <= m; i++)
		printf("%lld\n", ans[i]);
	return 0;
}


3.3 买礼物(线段树)

题意:判断给定的区间内有没有重复的两个数。
在这里插入图片描述
题目链接

分析:
前面两道题是判断给定区间内不同数的和或者异或和,这道题的要求其实是要更低,只需要判断出来区间有没有不同的两个数。但是与前面不同的是这道题询问之间掺杂着修改操作,也因此与前两到题有所区别。

用 nxt[ i ] 和 pre[ i ] 记录和第i个位置相同的数字的前面一个和后面一个的位置。这样对于每个询问 [l, r],只需要查询到这个区间内nxt[]的最小值,判断是否大于r即可。
至于取走一个数的操作,只需要修改这个点的nxt[]值INF,如果这个点有前驱节点,还要修改前驱的nxt[]值,这个过程可以类比链表删除节点的操作。
至此,问题已经解决,nxt[] 可以用线段树来维护。

#include<bits/stdc++.h>
using namespace std;

const int INF = 0x3f3f3f3f;

const int N = 500000 + 10;
const int M = 1000000 + 10;

int n, q;
int a[N];
int pre[N], nxt[N], pos[M];

struct ST{
	int l, r;
	int minn, lazy; //lazy标识"该节点"曾经被修改,但其子节点尚未被修改。
};                  //这题是单点修改(只修改叶子节点),所以可以不需要lazy标记(也自然不需要spread函数)的。
ST st[4*N];

void build(int p, int l, int r)
{
	st[p].l = l, st[p].r = r, st[p].lazy = 0;
	if (l == r)
	{
		st[p].minn = nxt[l];
		return;
	}
	int mid = (l + r) >> 1;
	build(p << 1, l, mid);
	build(p << 1 | 1, mid + 1, r);
	st[p].minn = min(st[p << 1].minn, st[p << 1 | 1].minn);
}

void spread(int p)
{
	if (st[p].lazy)
	{
		st[p << 1].minn = st[p].lazy;
		st[p << 1 | 1].minn = st[p].lazy;
		st[p << 1].lazy = st[p].lazy;
		st[p << 1 | 1].lazy = st[p].lazy;
		st[p].lazy = 0;
	}
}

void change(int p, int l, int r, int d)
{
	if (l <= st[p].l && st[p].r <= r)
	{
		st[p].minn = d;
		st[p].lazy = d;
		return;
	}
	spread(p);
	int mid = (st[p].l + st[p].r) >> 1;
	if (l <= mid)
	    change(p << 1, l, r, d);
	if (mid < r)
	    change(p << 1 | 1, l, r, d);
	st[p].minn = min(st[p << 1].minn, st[p << 1 | 1].minn);
}

int ask(int p, int l, int r)
{
	if (l <= st[p].l && st[p].r <= r)
	    return st[p].minn;
	spread(p);
	int mid = (st[p].l + st[p].r) >> 1;
	int ans = INF;
	if (l <= mid)
	    ans = min(ans, ask(p << 1, l, r));
	if (mid < r)
	    ans = min(ans, ask(p << 1 | 1, l, r));
	return ans;
}

int main()
{
	ios::sync_with_stdio(false);
	cin >> n >> q;
	for (int i = 1; i <= n; i++)
	{
	    cin >> a[i];
	    pre[i] = 0;
	    nxt[i] = INF;
	}
	
	for (int i = 1; i <= n; i++)
	{
		if (pos[a[i]])
		{
			pre[i] = pos[a[i]];
			nxt[pos[a[i]]] = i;
		}
		pos[a[i]] = i;
	}
	
	build(1, 1, n);
	
	while(q--)
	{
		int op;
		cin >> op;
		if (op == 1)
		{
			int x;
			cin >> x;
			
			int temp = pre[x];
			if (pre[x] != 0)
			    nxt[pre[x]] = nxt[x];
			if (nxt[x] != INF)
			    pre[nxt[x]] = pre[x];
			pre[x] = 0;
			nxt[x] = INF;
			
			change(1, x, x, INF);
			if (temp != 0)
			    change(1, temp, temp, nxt[temp]);
		}
		else
		{
			int l, r;
			cin >> l >> r;
			if (ask(1, l, r) <= r)
		    	cout << 1 << endl;
		    else
		        cout << 0 << endl;
		}
	}
	
	return 0;
}

4 数学

4.1 [SDOI2009]E&D(博弈论)

在这里插入图片描述
题目链接

分析:这题2k与2k-1这两堆可以看作一个ICG,最后要求的就是n个ICG的sg函数异或和,所以只要能求出每一组ICG的sg函数即可。
但是这里1<=N<=2e4, N为偶数, 1<=Si<=2e9,暴力求解每个ICG的sg是不行的。因此打表找规律。
记忆化搜索打表的代码:

#include<bits/stdc++.h>
using namespace std;
#define Min(a,b,c)  min(a,min(b,c))
#define Max(a,b,c)  max(a,max(b,c))
#define fi first
#define se second

typedef long long ll;
typedef unsigned long long ull;
const int INF = 0x3f3f3f3f;
const double pi = 3.1415926535898;
const double eps = 1e-8;

int sg[60][60];
int vis[60][60];

int mex(auto v) 
{
	unordered_set<int> S;
	for(auto e : v)
	    S.insert(e);
	for(int i = 0; ; i++)
	    if(S.find(i) == S.end())
	        return i; 
}

int dfs(int x, int y)
{
	vector<int> v;
	if (x == 1 && y == 1)
	    return 0;
	for (int i = 1; i < x; i++)
	{
		int j = x - i;
		if (vis[i][j] == 0)
		{
		    dfs(i, j);
		    vis[i][j] = 1;
		}
		v.push_back(sg[i][j]);
	}
	if(x != y)
	{
		for (int i = 1; i < y; i++)
    	{
	    	int j = y - i;
	    	if (vis[i][j] == 0)
	    	{
		        dfs(i, j);
		        vis[i][j] = 1;
		    }
	    	v.push_back(sg[i][j]);
    	}
	} 
	sg[x][y] = mex(v);
}

int main()
{
	freopen("table.txt", "w", stdout);
	sg[1][1] = 0;
	vis[1][1] = 1;
	for (int i = 1; i <= 30; i++)
	{
		for (int j = 1; j <= 30; j++)
		{
			if (i == j && i == 1)
			    continue;
			dfs(i, j);
		}
	}
	for (int i = 1; i <= 30; i++)
	{
	    for(int j = 1; j <= 30; j++)
	        cout << sg[i][j] << ' ';
	    cout << endl;
	}
	return 0;
}

表如下:
在这里插入图片描述
在表格中,可以发现第2i个副对角线上的数字都是第2i-1个副对角线上的数字加1。所以大胆猜测,对于除了既是奇数行又是奇数列的单元格(a, b), sg(a, b) = sg((上整数)a/2, (上整数)b/2) + 1。

解题代码:

#include<bits/stdc++.h>
using namespace std;
#define Min(a,b,c)  min(a,min(b,c))
#define Max(a,b,c)  max(a,max(b,c))
#define fi first
#define se second

typedef long long ll;
typedef unsigned long long ull;
const int INF = 0x3f3f3f3f;
const double pi = 3.1415926535898;
const double eps = 1e-8;

int a[20020];

int up(int a, int b)
{
	if (a % b == 0)
	    return a / b;
	return a / b + 1;
}

int sg(int a, int b)
{
	if ((a&1) && (b&1))
		return 0;
	return sg(up(a, 2), up(b, 2)) + 1;
}

int main()
{
	ios::sync_with_stdio(false);
	int t;
	cin >> t;
	while (t--)
	{
		int n;
		cin >> n;
		int ans = 0;
		for (int i = 1; i <= n; i++)
		{
		    cin >> a[i];
		    if (i % 2 == 0)
		    ans ^= sg(a[i-1], a[i]);
		}
		if (ans)
		    cout << "YES" << endl;
		else
		    cout << "NO" << endl;
	}
	return 0;
}

时间复杂度O(nlogS)。

5. 图论

5.1 Paired Payment(最短路)

题意:无向图求结点1到n个结点的最短路。限制条件:每次必须走两条边(必须途经另外一个点)
在这里插入图片描述

题目链接

分析:
参考博客
有一种暴力的想法就是直接遍历每个点,把这个点的出边的终点的所有出边终点连边,但是复杂度达到O(n2),显然是不合适的。
下面是正解:
考虑边权值最大只有50,把n个结点扩展为51*n个结点,即原来的每个结点都派生出50个结点,我们用 (i, j) 表示第 i 个结点派生出的第 j 个(0 <= j <= 50) 结点,j = 0 时表示原生第 i 个结点。那么在 j != 1的情况下,(i, j)表示的是 以权值为 j 的边到达 i 的 结点。
接下来对于每条边(x,y,z),建单向边<x,(y,z),0>,<y,(x,z),0>,<(x,i),y,(z+i)2>,<(y,i),x,(z+i)2> (1<=i<=50)
跑Dijkstra即可。

#include<bits/stdc++.h>
using namespace std;
#define fi first
#define se second

const int INF = 0x3f3f3f3f;
const int N = 5100010;
const int M = 20400010;

int n, m;
int head[N], ver[M], nxt[M], edge[M], tot;
int v[N], d[N];

void add(int x, int y, int z)
{
	ver[++tot]=y, nxt[tot]=head[x], head[x]=tot, edge[tot]=z;
}

void dij()
{
	memset(d, INF, sizeof(d));
	priority_queue<pair<int, int>, vector<pair<int, int> >, greater<pair<int, int> > > pq;
	pq.push(make_pair(0, 1));
	d[1] = 0;
	while(pq.size())
	{
		int x = pq.top().se;
		pq.pop();
		if (v[x])
		    continue;
		v[x] = 1;
		for (int i = head[x]; i; i = nxt[i])
		{
			int y = ver[i], z = edge[i];
			if (d[x] + z < d[y])
			{
				d[y] = d[x] + z;
				pq.push(make_pair(d[y], y));
			}
		}
	}
}

int main()
{
	scanf("%d%d", &n, &m);
	memset(head, 0, sizeof(head));
	tot = 0;
	for (int i = 1; i <= m; i++)
	{
		int x, y, z;
		cin >> x >> y >> z;
		x = (x - 1) * 51 + 1, y = (y - 1) * 51 + 1;
		add(x, y + z, 0), add(y, x + z, 0);
		for (int i = 1; i <= 50; i++)
		{
			add(x + i, y, (i + z) * (i + z));
			add(y + i, x, (i + z) * (i + z));
		}
	}
	dij();
	for (int i = 1; i <= n; i++)
	{
	    printf("%d", d[(i - 1) * 51 + 1] == INF ? -1 : d[(i - 1) * 51 + 1]);
		i == n ? printf("\n") : printf(" ");
	}
	return 0;
}

5.2 World Tour(最短路)

题意:不加权有向图中选四个不同的有序的点,从第一个点依次经过到达第四个点的三段路程的最短路之和最大。
在这里插入图片描述
分析:
a->b->c->d的最短路之和最大,考虑到n<=3000,先预处理

  1. 每个点到其余所有点的最短路
  2. 到每个点最短路最大的三个点
  3. 从每个点出发的最短路最大的三个点

然后枚举b和c,再依次枚举a和d,a和b可以直接枚举之前预处理出来的3个最大的点。
求最短路因为有向图无权,所以可以bfs求更快,预处理为O(n2);
枚举不超过9*n2次。

代码:

#include<bits/stdc++.h>
using namespace std;

#define Min(a,b,c)  min(a,min(b,c))
#define Max(a,b,c)  max(a,max(b,c))
#define fi first
#define se second

typedef long long ll;
typedef unsigned long long ull;
const int INF = 0x3f3f3f3f;
const double pi = 3.141592653589793;
const double eps = 1e-8;

const int N = 3010;
const int M = 5010;

int n, m;
int head[N], ver[M], nxt[M], tot;
int d[N][N];
pair<int, int> st[N][4], to[N][4];

struct node{
	int a, b, c, d;
	int val;
};
node ans;

void add(int x, int y)
{
	ver[++tot]=y, nxt[tot]=head[x], head[x]=tot;
}

void bfs(int id)
{
	queue<int> q;
	q.push(id);
	while(q.size())
	{
		int x = q.front();
		q.pop();
		
		for (int i = head[x]; i; i = nxt[i])
		{
			int y = ver[i];
			if (d[id][y] || y == id)
			    continue;
			d[id][y] = d[id][x] + 1;
			q.push(y);
		}
		
		if (id == x)
		    continue;
		    
		pair<int, int> temp = make_pair(d[id][x], x);
		if (temp > st[id][1])
		    st[id][3] = st[id][2], st[id][2] = st[id][1], st[id][1] = temp;
		else if (temp > st[id][2])
		    st[id][3] = st[id][2], st[id][2] = temp;
		else if (temp > st[id][3])
		    st[id][3] = temp;
		    
		temp = make_pair(d[id][x], id);
		if (temp > to[x][1])
		    to[x][3] = to[x][2], to[x][2] = to[x][1], to[x][1] = temp;
		else if (temp > to[x][2])
		    to[x][3] = to[x][2], to[x][2] = temp;
		else if (temp > to[x][3])
		    to[x][3] = temp;
		
	}
}

int main()
{
	cin >> n >> m;
	memset(head, 0, sizeof(head));
	tot = 0;
	for (int i = 1; i <= m; ++i)
	{
		int u, v;
		cin >> u >> v;
		if (u != v)
		    add(u, v);
	}
	memset(d, 0, sizeof(d));
	for (int i = 1; i <= n; i++)
	    bfs(i);
	ans.val = 0;
	for (int i = 1; i <= n; i++)
	{
		for (int j = 1; j <= n; j++)
		{
			if (!d[i][j])
			    continue;
			for (int k = 1; k <= 3; k++)
				for (int l = 1; l <= 3; l++)
				{
					int u = to[i][k].second;
					int v = st[j][l].second;
					if (u == 0 || v == 0 || u == j || i == v || u == v)
					    continue;
					node temp;
					temp.val = d[i][j] + d[u][i] + d[j][v];
					temp.a = u, temp.b = i, temp.c = j, temp.d = v;
					if (temp.val > ans.val)
					    ans = temp;
				}
		}
	}
	cout << ans.a << ' ' << ans.b << ' ' << ans.c << ' ' << ans.d << endl;
	return 0;
}

 
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值