Codeforces Round #719 div3 A-G题解
文章目录
A Do Not Be Distracted!
题意为有一个字符串s,让你判断是否有字符没有连续出现,即两个相同的字符之间有其他的字符,没有的话输出YES,否则输出NO。
用map记录下每一个字符出现的最后一个位置,当遍历到当前字符且当前字符出现过时,只需要判断当前字符的前一次出现的位置是否为当前位置的前一位,是的话就满足,否则的话就不满足。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int t,n;
string s;
int main()
{
cin >> t;
while(t--){
cin >> n >> s;
if(n==1){
cout << "YES" << endl;
continue;
}
map<char,int> idx;
bool flag = true;
for(int i=0;i<n;i++){
if(idx.count(s[i])){
if(idx[s[i]]+1 == i ) idx[s[i]] = i;
else flag = false;
}
else{
idx[s[i]] = i;
}
}
if(flag) cout << "YES" << endl;
else cout << "NO" << endl;
}
return 0;
}
B Ordinary Numbers
给定n,问你小于n的正整数中个位数字相同的数的个数
随便写了个dfs
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int t,n;
int dfs(int u)
{
if(u > n) return 0;
int idx = u % 10;
ll p = 1ll * u * 10 + idx;
if(p > n) return 1;
return 1 + dfs(p);
}
int main()
{
cin >> t;
while(t--){
cin >> n;
int ans = 0;
for(int i=1;i<=9;i++) ans += dfs(i);
cout << ans << endl;
}
return 0;
}
C Not Adjacent Matrix
给定n<=100,问是否存在一个nn的矩阵使得矩阵里的数为1 - nn且每个数只出现一次并且矩阵相邻的元素的值不能相邻。若存在输出矩阵,否则输出-1。
显然n = 1时直接输出1
n = 2时无解输出-1
n >= 3 时
可以考虑如下构造
在每一行隔一个数按顺序填入1,2,3…下一行隔一个数接着填入,以此类推,可以保证相邻的数不相等。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int t,n;
int mp[110][110];
int main()
{
cin >> t;
while(t--){
cin >> n;
if(n==1) cout << "1" << endl;
else if(n==2) cout << "-1" << endl;
else{
memset(mp,0,sizeof mp);
int hang = 1,idx = 1;
int now;
for(int i=1;i<=n*n;i++){
mp[hang][idx] = i;
idx += 2;
if(idx > n){
hang ++;
if(hang&1) idx = 1;
else idx = 2;
}
if(hang > n) {
now = i+1;
break;
}
}
hang = 1;
idx = 2;
for(int i=now;i<=n*n;i++){
mp[hang][idx] = i;
idx += 2;
if(idx > n){
hang ++;
if(hang&1) idx = 2;
else idx = 1;
}
if(hang > n) break;
}
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++)
cout << mp[i][j] << " ";
cout << endl;
}
}
}
return 0;
}
D Same Differences
给定长度为n(n<=2e5)的数组a1,a2,…an,求出(i,j)的数量满足1<=i<j<=n且aj-ai = j-i
显然原式可以化为aj - j == ai - i
所以只需要统计ai - i 的数出现的次数,并用map保存相加即可
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int t,n,a[200010];
map<int,int> mp;
int main()
{
cin >> t;
while(t--){
cin >> n;
mp.clear();
for(int i=1;i<=n;i++) cin >> a[i];
ll ans = 0;
for(int i=1;i<=n;i++){
if(mp.count(a[i]-i)) ans += mp[a[i]-i];
mp[a[i]-i] ++;
}
cout << ans << endl;
}
return 0;
}
E Arranging The Sheep
给定一个长度为n(n <= 1e6)的字符串,字符串由 . 和 * 构成,.代表空位置,*代表羊,可以将羊移动到周围没有羊的空位置,问最少需要移动多少次即可将所有羊移动成相邻的羊。
可以枚举完成移动后第一只羊的位置,对于其他所有羊的移动的值可以通过前缀和维护来达到o(n)的时间复杂度。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1e6+10;
const ll inf = 0x3f3f3f3f3f3f3f3f;
int t,n;
string s;
int sum;
ll pos[maxn];
ll xishu[maxn];
ll qis[maxn];
void solve()
{
sum = 0;
for(int i=0;i<n;i++){
if(s[i]=='*') pos[++sum] = i + 1;
}
if(sum == 0) {
printf("0\n");
return;
}
for(int i=1;i<=sum;i++) xishu[i] = 1 + pos[i] - i;
sort(xishu+1,xishu+sum+1);
for(int i=1;i<=sum;i++) qis[i] = qis[i-1] + xishu[i];
ll minres = inf;
ll maxx = n + 1 - sum;
if(xishu[1] > 0 && xishu[1] <= maxx){
minres = min(minres,1ll * qis[sum]-qis[1]);
}
for(int i=1;i<sum;i++){
if(2*i - sum > 0){
int x = max(1ll,xishu[i]);
if(x > maxx) continue;
else minres = min(minres,1ll * (2*i-sum)*x+qis[sum]-2*qis[i]);
}
else{
int x = min(maxx,xishu[i+1]);
if(x <= 0) continue;
else minres = min(minres,1ll * (2*i-sum)*x+qis[sum]-2*qis[i]);
}
}
if(xishu[sum] <= maxx) {
minres = min(minres,1ll * xishu[sum] * sum - qis[sum]);
}
printf("%lld\n",minres);
}
int main()
{
cin >> t;
while(t--){
cin >> n >> s;
solve();
}
return 0;
}
F1 Guess the K-th Zero (Easy version)
交互题,有一个长度为n(n<=2e5)的数列仅由0 1构成让你猜这个数列中第k个0的位置是多少
你可以用? l r 的方式来询问区间l-r的和为多少,做多可以问20次。
当知道答案后可以用! x的方式输出答案
由于是简单版本,所以题目中只让你猜第k个0的位置一次,即t = 1
根据20次不难想到可以用二分来写,具体的二分类似于权值线段树中求第k大的数
#include <bits/stdc++.h>
using namespace std;
int n,t,k;
void binary_search(int l,int r,int kk)
{
if(l==r){
printf("! %d\n",l);
fflush(stdout);
return;
}
int m = (l+r) >> 1;
printf("? %d %d\n",l,m);
fflush(stdout);
int res;
scanf("%d",&res);
int sum0 = m - l + 1 - res;
if(sum0 >= kk) binary_search(l,m,kk);
else binary_search(m+1,r,kk-sum0);
}
int main()
{
scanf("%d%d",&n,&t);
while (t--)
{
scanf("%d",&k);
binary_search(1,n,k);
}
return 0;
}
F2 Guess the K-th Zero (Hard version)
作为上一题的困难版本,又多加了一条规则,对于这个数组会询问t(t<=1e4)次,每次给你一个k,每次当你回答后如果你回答正确会将你当前回答的位置的0变为1,并接着进行下次提问。最多可以询问6e4次。
首先根据数组的长度2e5不难发现对于每次二分都需要提问log n 次,而t的范围可以达到1e4,如果按照上一题的方法来写询问次数大概为1e4 * log 2e5 肯定会超过6e4,因此要对于算法进行优化。
首先1e4次的提问肯定无法改变那么我们就要改变每次提问时需要的次数,即将我们每次提问时需要查询的范围缩小,原先查询的范围是1到n,我们可以先用着6e4次询问机会中的一部分先预处理一些区间的结果以此来缩小查询的长度,以此来改变每次查询需要的次数。
如果我们把查询的区间长度缩小为2,那么每次查询只需要1次,但是预处理出每两个数之间的和需要的查询次数为n/2次即1e5次,超过了可以查询的次数。
如果把查询的区间长度缩小为4,那么每次查询需要2次,共计2e4次,而预处理所需要的次数为n/4次,即5e4次,相加为7e4次,还是略微比目标大一些
如果把查询的区间长度缩小为8,那么每次查询需要3次,共计3e4次,而预处理所需要的次数为n/8次,即2.5e4次,相加为5.5e4次,满足我们所需要的条件。
因此在回答前每隔8个数询问一次1到i的前缀和,以此来了解前方有多少0,并一次来确定每次需要查询的第k个0到底在哪个区间,要注意的是由于每次会将回答到的0变为1,因此在每次回答前需要将之前预处理得到的数组与回答的位置进行比较,观察是否会影响前缀和。
#include <bits/stdc++.h>
using namespace std;
const int inf = 0x3f3f3f3f;
int n,t,k;
unordered_map<int,int> mp;
int num0[200010];
int cnt = 0;
int laspos;
void binary_search(int l,int r,int kk)
{
if(l==r){
laspos = l;
printf("! %d\n",l);
fflush(stdout);
return;
}
int m = (l+r) >> 1;
printf("? %d %d\n",l,m);
fflush(stdout);
int res;
scanf("%d",&res);
int sum0 = m - l + 1 - res;
if(sum0 >= kk) binary_search(l,m,kk);
else binary_search(m+1,r,kk-sum0);
}
int main()
{
mp[0] = 0;
scanf("%d%d",&n,&t);
scanf("%d",&k);
for(int i=8;i<=n;i+=8){
printf("? 1 %d\n",i);
fflush(stdout);
int res;
scanf("%d",&res);
mp[i] = res;
//num0[++cnt] = res;
}
for(int kase=1;kase<=t;kase++)
{
if(kase != 1){
scanf("%d",&k);
for(auto i:mp){
int idx = i.first;
if(idx < laspos) continue;
else mp[idx] ++;
}
}
int l=inf,r=inf;
int i;
for(i=8;i<=n;i+=8){
if(i-mp[i] < k) continue;
l = i - 7;
r = i;
k -= i - 8 - mp[i-8];
break;
}
i -= 8;
if(l == inf){
l = max(1,n-7);
r = n;
k -= i-mp[i];
}
binary_search(l,r,k);
}
return 0;
}
呜呜呜,一开始的时候用map T了,本来想改为数组模拟的,结果试了一下unordered_map居然就可以过了。
G To Go Or Not To Go?
给定一个n*m的地图,地图上如果为-1即为不可走,0表示可以走,>0的位置表示传送门,可以传到任何一个其他的传送门(也可以选择不传送,没有花费),并花费两个传送门上的数字的和的代价。每普通向周围走一步需要付出w的代价,求从(1,1)到(n,m)的最小费用
显然如果走传送门的话最好多只会走一次传送门(。。。传多了那纯属钱多好吧)
那么可以分为两类
- 不通过传送门传送,直接bfs即可
- 通过一个传送门,可以通过bfs求出起点到每个传送门的最小花费以及终点到达每个传送门得最小花费,两个答案相加即可。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
const ll inf = 0x3f3f3f3f3f3f3f3f;
int n,m;
ll w;
ll a[2010][2010];
int dx[] = {0,0,1,-1};
int dy[] = {1,-1,0,0};
ll res1[2010][2010];
ll res2[2010][2010];
bool vis[2010][2010];
bool isok(int x,int y)
{
return x>=1 && x <= n && y >= 1 && y <= m;
}
void solve()
{
memset(res1,inf,sizeof res1);
memset(res2,inf,sizeof res2);
ll res = inf;
ll min1 = inf,min2 = inf;
queue<pair<pii,ll> > q;
q.push(make_pair(make_pair(1,1),0));
vis[1][1] = false;
res1[1][1] = 0;
while ( !q.empty() )
{
pair<pii,ll> p = q.front();
q.pop();
pii pp = p.first;
int x = pp.first;
int y = pp.second;
if(a[x][y]>0) min1 = min(min1,p.second+a[x][y]);
for(int i=0;i<4;i++){
int xx = x + dx[i];
int yy = y + dy[i];
if(!isok(xx,yy) || a[xx][yy] == -1 || vis[xx][yy]) continue;
q.push(make_pair(make_pair(xx,yy),p.second+w));
res1[xx][yy] = p.second + w;
vis[xx][yy] = true;
}
}
if(res1[n][m] != inf) res = res1[n][m];
while (!q.empty()) q.pop();
memset(vis,false,sizeof vis);
q.push(make_pair(make_pair(n,m),0));
vis[n][m] = true;
res2[n][m] = 0;
while ( !q.empty() )
{
pair<pii,ll> p = q.front();
q.pop();
pii pp = p.first;
int x = pp.first;
int y = pp.second;
if(a[x][y] > 0) min2 = min(min2,res2[x][y]+a[x][y]);
for(int i=0;i<4;i++){
int xx = x + dx[i];
int yy = y + dy[i];
if(!isok(xx,yy) || a[xx][yy] == -1 || vis[xx][yy]) continue;
q.push(make_pair(make_pair(xx,yy),p.second+w));
res2[xx][yy] = p.second + w;
vis[xx][yy] = true;
}
}
//cout << "min1: " << min1 << endl << "min2: " << min2 << endl;
res = min(res,min1+min2);
if(res >= inf) printf("-1");
else printf("%lld",res);
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
cin >> n >> m >> w;
for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) cin >> a[i][j];
solve();
return 0;
}
呜呜呜终于见到一次友好的div3了。