Codeforces Round #719 (Div. 3) 题解

https://codeforces.com/contest/1520

在B题上卡了一下,我是SB。

A题

题意:
就问你有没有字母不是连续着出现

思路:
直接判断即可

string s;
int n;
int cnt[26];
 
int main(){
	
	int T;
	cin >> T;
	while(T--)
	{
		memset(cnt, 0, sizeof cnt);
		cin >> n;
		cin >> s;
		bool flag = true;
		for(int i = 0 ; i < n ; i ++)
		{
			if(i && s[i - 1] != s[i])
				if(cnt[s[i] - 'A'])
				{
					flag = false;
					break;
				}
			cnt[s[i] - 'A'] ++;
		 } 
		if(flag) cout << "YES\n";
		else cout << "NO\n";
	}
	
	return 0;
}

B题

题意:问你从1到n要多少个数是数的每位数字都相等

思路:
直接枚举相同的数字是\(X_i\),然后不断进行\(X_i = X_i * 10 + X_i\)操作,直到数字大于n,统计数目即可。

ll n, k;
 
int main(){
	IOS;
	int T;
	cin >> T;
	while(T--)
	{
		cin >> n;
		ll ans = 0;
		for(ll i = 1 ; i <= 9 ; i ++)
		{
			ll temp = 0;
			while(temp * 10 + i <= n)
			{
				temp = temp * 10 + i;
				ans ++;
			}
		}
		cout << ans <<"\n";
	}
	
	return 0;
}

开始错误的代码
这个写法错在了特判最高位是否可行处

ll n;
vector<int> v;
 
int main(){
	IOS;
	int T;
	cin >> T;
	while(T--)
	{
		cin >> n;
		while(n)
		{
			v.push_back(n % 10);
			n /= 10;
		}
		reverse(v.begin(), v.end());
		int ans = (v.size() - 1) * 9 + v[0] - 1;
		bool flag = true;
		int d = v[0];
		for(auto x : v)  //此处错了
			if(x < d) flag = false;
		if(flag) ans ++;
		cout << ans << "\n";
		v.clear();
	}
	
	return 0;
}

C题

题意:让你将 n * n个数放进 n * n的矩阵里,使得相邻位置的数数值不相邻

思路:
按照顺序,先放奇数,再放偶数,最后判断是否合法即可。

int n;
int g[N][N];
int dx[] = {1, 0, -1, 0};
int dy[] = {0, 1, 0, -1};
 
bool check(int a, int b)
{
	int c = g[a][b];
	for(int i = 0 ; i < 4 ; i ++)
	{
		int x = a + dx[i];
		int y = b + dy[i];
		if(x < 1 || x > n || y < 1 || y > n) continue;
		if(abs(g[x][y] - c) == 1) return false;
	}
	return true;
}
 
int main(){
	IOS;
	int T;
	cin >> T;
	while(T--)
	{
		cin >> n;
		int lim = n * n;
		int l = 1, r = 2;
		for(int i = 1 ; i <= n ; i ++)
			for(int j = 1 ; j <= n ; j ++)
			{
				if(l <= lim) g[i][j] = l, l += 2;
				else g[i][j] = r, r += 2;
			}
		bool flag = true;
		for(int i = 1 ; i <= n ; i ++)
		{
			if(!flag) break;
			for(int j = 1 ; j <= n ; j ++)
			{
				if(!check(i, j)) flag = false;
				break;
			}
		}
		
		if(!flag) cout << "-1\n";
		else
		{
			for(int i = 1 ; i <= n ; i ++)
			{
				for(int j = 1 ; j <= n ; j ++)
					cout << g[i][j] << ' ';
				cout << '\n';				
			}
		}
	}
	
	return 0;
}

D题

题意:
给你一个序列,问你有多少序列满足\(i < j\)并且$ a_j - a_i = j - i $。

思路:
将式子变形,变成$ a_j - j = a_i - i $,就非常简单啦

int n;
int a[N];
map<ll, ll> mp;
 
int main(){
	IOS;
	int T;
	cin >> T;
	while(T--)
	{
		cin >> n;
		for(int i = 1 ; i <= n ; i ++)
		{
			cin >> a[i];
			a[i] -= i;
			mp[a[i]] ++;
		}
		
		ll ans = 0;
		for(auto x : mp) ans += x.y * (x.y - 1) / 2;
		cout << ans << "\n";
		mp.clear();
	}
	
	return 0;
}

E题

题意:给你一个字符串,里面有 . 和 * ,每次可以移动一个到.处,问你最少移动多少次可以让所有连续。

思路:
直接线性dp即可,因为你左边怎么凑过来的和右边怎么凑过来的无关,只需要求左边加右边最小即可。

int n;
string s;
pll l[N];
pll r[N];
 
int main(){
	IOS;
	int T;
	cin >> T;
	while(T--)
	{
		cin >> n >> s;
		l[0].y = (s[0] == '*');
		for(int i = 1 ; i < n ; i ++)
		{
			if(s[i] != '*')
			{
				l[i].x = l[i - 1].x + l[i - 1].y;
				l[i].y = l[i - 1].y;
			}
			else l[i] = l[i - 1];
			if(s[i] == '*')
				l[i].y ++;
		}
		
		r[n - 1].y = (s[n - 1] == '*');
		for(int i = n - 2 ; i >= 0 ; i --)
		{
			if(s[i] != '*')
			{
				r[i].x = r[i + 1].x + r[i + 1].y;
				r[i].y = r[i + 1].y;				
			}
			else r[i] = r[i + 1];
			if(s[i] == '*')
				r[i].y ++;
		} 
		
		ll ans = 1e18;
		for(int i = 0 ; i < n ; i ++)
			ans = min(ans, l[i].x + r[i].x);
		cout << ans << "\n";
		for(int i = 0 ; i < n ; i ++)
			l[i].x = l[i].y = r[i].x = r[i].y = 0;
	}
	
	return 0;
}

F1题

题意:
有一个隐藏的数组,只有0和1,让你每次查询一个区间,每次系统会返回区间的和,让你在20次查询内找到数组中的第k个0。

思路:
显然是二分,二分查找即可,详情看如下代码。

int n, t, k;
int sum;
 
int main(){
	IOS;
	cin >> n >> t;
	cin >> k;
	
	int l = 1, r = n;
	while(l < r)
	{
		int mid = l + r >> 1;
		cout << "? " << l << " " << mid << "\n";
		cout.flush();
		cin >> sum;
		if(sum == 0)
		{
			cout << "! " << l + k - 1 << "\n";
			cout.flush();
			return 0;
		}
		if(sum + k <= mid - l + 1)	r = mid;
		else k -= (mid - l + 1 - sum), l = mid + 1;
	}
	cout << "! " << l << "\n";
	cout.flush(); 
	return 0;
}

F2题

这题懒得写了,转载自我儿子的博客
题意:
交互(复杂版本),再F1的基础上多了几次需要查询的k值,并且每次查询之后需要将该处的0变为1,询问的上界为\(6∗10^4\)

思路:
根据F1查询的思路,发现我们是需要使用到前i个数中1出现的次数,也就是前缀和,那么我们可以首先依次询问[1,R],(1≤R≤n)然后维护一下前缀和,再对每个查询用二分查找。
但是这样由于\(n≤2∗10^5\),如果依次询问必定会超过上界,因此可以将区间[1,n]分块,维护一下分块的前缀和,然后对每个边界二分询问即可。设分块大小为x,则\(nx+m∗logx≤6∗10^4\),化简之后得\(20x+logx≤6\),解得近似\(5≤x≤30\),对时间复杂度进行分析之后发现取\(x=30\)最好。
注意:我们维护的是分块边界,内部并没有维护,因此k需要减去的是边界处对应的值。

const int N = 200100;

int tr[N];
int n, m, k;

int lowbit(int x) 
{
    return x & -x;
}

int ask(int x) 
{
    int res = 0;
    for (int i = x; i; i -= lowbit(i)) res += tr[i];
    return res;
}

void add(int x, int d) 
{
    for (int i = x; i <= n; i += lowbit(i)) tr[i] += d;
}

int main() 
{
    cin >> n >> m;
    
    for (int i = 1; i <= m; i ++ ) 
    {
        cin >> k;
        if (i == 1) 
        {
            int sum1 = 0, sum2 = 0;
            for (int i = 1; i < n; i += 30) 
            {
                cout << "? " << 1 << " " << i << endl;
                cout.flush();

                cin >> sum2;
                add(i, sum2 - sum1);
                sum1 = sum2;
            }
            cout << "? " << 1 << " " << n << endl;
            cout.flush();

            cin >> sum2;
            add(n, sum2 - sum1);
        }

        int l = 1, r = n;
        for (int i = 1; i < n; i += 30) 
            if (i - ask(i) >= k)
            {
                l = max(1, i - 30) + 1;
                r = i;
                k -= l - 1 - ask(l - 1);
                break;
            }

        while (l < r) 
        {
            int sum;
            int mid = l + r >> 1;
            cout << "? " << l << " " << mid << endl;
            cout.flush();
            cin >> sum;

            if (mid - l + 1 - sum >= k) r = mid;
            else k -= mid - l + 1 - sum, l = mid + 1;
        }

        cout << "! " << r << endl;
        cout.flush();
        add(r, 1);
    }
    cin >> k;

    return 0;
}

G题

题意:
给你一个网格图,含有有障碍物和传送门,走到相邻格和传送都有一定的花费,传送门可以到达其它任意传送门,问你从起到到终点的最小花费是多少。

思路:
由于传送门的数量巨大,我们不可能一对一对枚举传送门,因此采用双向处理,最后再取一下min(ans, l + r),其中l + r是从两边使用传送门的最小花费

int n, m, worth;
int g[N][N];
int arr[N][N], cnt;
vector<pii> v; 
vector<int> s[3];
ll dist1[M], dist2[M];
int val[M];
bool st[M];
int dx[] = {0, 1, 0, -1};
int dy[] = {1, 0, -1, 0};
 
 
void bfs(int type, ll dist[])
{
	memset(st, 0, sizeof st);
	queue<pll> q;
	if(type == 1) dist[1] = 0, q.push({1, 1});
	else dist[n * m] = 0, q.push({n, m});
	
	while(q.size())
	{
		auto t = q.front();
		q.pop();
		int x = t.x, y = t.y;
		if(g[x][y] > 0) s[type].push_back(arr[x][y]);
		if(st[arr[x][y]]) continue;
		st[arr[x][y]] = true;
		if(g[x][y] == -1) continue;
		for(int i = 0 ; i < 4 ; i ++)
		{
			int a = x + dx[i], b = y + dy[i];
			if(a < 1 || a > n || b < 1 || b > m) continue;
			if(g[a][b] == -1 || st[arr[a][b]]) continue;
			dist[arr[a][b]] = dist[arr[x][y]] + worth;
			q.push({a, b});				
		}
	}
}
 
int main()
{
	IOS; 
	cin >> n >> m >> worth;
	for(int i = 1 ; i <= n ; i ++)
	{
		for(int j = 1 ; j <= m ; j ++)
		{
			cin >> g[i][j];
			arr[i][j] = ++ cnt;
			val[cnt] = g[i][j];
		}		
	}
	memset(dist1, 0x3f, sizeof dist1);
	memset(dist2, 0x3f, sizeof dist2);
	bfs(1, dist1);
	bfs(2, dist2);
	
	ll res = min(dist1[n * m], dist2[1]);
	ll l = 1e18, r = 1e18;
	for(auto x : s[1]) l = min(l, dist1[x] + val[x]);
	for(auto x : s[2]) r = min(r, dist2[x] + val[x]);
	res = min(res, l + r);
	if(res >= 1e18) res = -1;
	cout << res << endl;
	return 0;
}
posted @ 2021-05-06 01:36  beatlesss  阅读(128)  评论(0)    收藏  举报