AtCoder Beginner Contest 313

D - Odd or Even 

​​​​​​​

题意:

交互题,给定一个N、K(K为奇数),有一个长度为N的只包含数字0、1的序列A=(A1​,A2​,…,AN​) ,你可以最多向评测机询问N次,每次询问K个数(x1,x2...xk),每次询问评测机会向你返回Ax1​​+Ax2​​+⋯+AxK的值​。能否在N次询问内确定这个序列A,并输出这个序列。

关键:

1.因为序列只包含数字0、1,故每次询问中,可以把k个数的的和看作k个数做异或操作,而异或又有一些比较好的性质。我们可以采取以下的策略一定可以在N次内确定这个序列A。

2.因为异或具有交换律,而且奇数个相同的数异或得到的是本身,偶数个相同的数异或得到的是0,0异或任何数都是这个数本身,而K又恰好是奇数。

举个例子,如果K=3。我们可以通过(1,2,3),(1,2,4),(1,3,4)这三组询问做异或得到A1的值,因为次询问都包含1,所以1出现的次数一定是奇数次,而其他几个数都分别在某次询问中缺少过一次,故出现次数是偶数。

扩展到一般做法,我们可以先询问k+1次,并用ans[i]数组记录序列前k+1个数不包括Ai的异或值,那么A[i]=所有包括Ai的ans[j]做异或(j!=i且j>=1&&就j<=k+1)这样就能确定出序列的前k+1个值,从第k+2个数开始,因为前k+1个数已经确定,所以我们每次询问[3,k+2]在内的的k个数的异或值,在异或上已经确定的前[3,k+1]的值,就得到了A[k+2]的值,以此类推就得到了这个序列每个位置上的值。

实现代码
#include <string>
#include <algorithm>
#include <math.h>
#include <cmath>
#include <set>
#include <queue>
#include <cstring>
#include <map>
#include<iomanip>
#include<iostream>
#include<stack>
#include<unordered_set>
#define io ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
using namespace std;
typedef long long ll;
int n, k;
const int N = 1e3 + 8;
vector<int>ans(N);
void solve()
{
	vector<int>p;
	vector<int>T(N);
	for (int i = 1; i <= k + 1;i++)
	{
		p.clear();
		for (int j = 1; j <= k + 1; j++)
			if (j != i)
				p.push_back(j);
		cout << "?";
		for (auto k : p)
			cout << " " << k;
		cout << endl;
		cin>>T[i];
	}
	for (int i = 1; i <= k + 1; i++)
	{
		for (int j = 1; j <= k + 1; j++)
			if (j != i)
				ans[i] ^= T[j];
	}
	for (int i = k + 2; i <= n; i++)
	{
		cout << "?";
		for (int j = i; j >= i - k + 1; j--)
		{
			cout << " " << j;
		}
		cout << endl;
		cin >> ans[i];
		for (int j = i-1; j >= i - k + 1; j--)
		{
			ans[i] ^= ans[j];
		}
	}
}
int main()
{
	io;
	cin >> n >> k;
	solve();
	cout << "!";
	for (int i = 1; i <= n; i++)
		cout<<" " << ans[i];
	cout << endl;
	return 0;
}

E - Duplicate

题意:

给定一个长度为N的包含数字1-9的初始序列S。定义一种操作:对于当前序列,若序列T为空,若序列的长度为n,则对于i>=1&&i<=n-1,从前往后让S[i]复制S[i+1]次并接在T的末尾,这样就得到了一个新的序列T。重复这种操作,问需要多少次能让给定的初始序列长度变为1,若无论操作多少次都不能达到目标,则输出-1;

关键:

1.首先考虑特殊情况,什么样的序列的无论经过多少次这样的操作长度永远无法变为1?通过手模几次,我们可以发现,如果一个序列中某个位置的数大于1,并且这个数后面的那个数也大于1,那么会这个序列中的这两个数,经过每次操作后这一部分都会变得更长,那么对于整个序列来说,会一直变长,因为一次操作只能抹去该序列的最后一个数。所以这个序列一定是类似511131112116也就是说任意两个大于1的数中间一定至少含有一个1。

2.我们将整个序列拆成部分考虑,把整个序列都拆分成一系列的1+一个大于1的数m,例如1113,我们可以很容易发现,每次操作,这个大于1的数会让前面的数字1的长度增长(m-1)。于是我们可以从后往前去求解这个问题,我们可以用一个变量ans记录目前为止一共进行了多少次操作,然后我们从后往前遍历初始序列S,如果当前的数字为1,那么直接进行一次操作即可消除这个数字,并且ans++。如果当前的数字大于1(记为m),遍历到现在时,已经进行过ans次操作,那么这个大于1的数字会让前面的数字1的长度增加ans*(m-1)次,那么当前这个数字m对答案的贡献就是ans*(m-1)+1次,同时更新ans=(ans+ans*(m-1)+1)%mod。

实现代码
#include <string>
#include <algorithm>
#include <math.h>
#include <cmath>
#include <set>
#include <queue>
#include <cstring>
#include <map>
#include<iomanip>
#include<iostream>
#include<stack>
#include<unordered_set>
#define io ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
using namespace std;
const int N = 1e6 + 8;
int a[N];
typedef long long ll;
const int mod = 998244353;
int main()
{
    string str;
    int n;
    cin >> n;
    cin >> str;
    str = " " + str;
    for (int i = 1; i <= n; i++)
    {
        a[i] = str[i] - '0';
    }
    for (int i = 1; i < n; i++)
    {
        if (a[i] > 1)
        {
            if (a[i + 1] > 1)
            {
                cout << "-1";
                return 0;
            }
        }
    }
    int d = n;
    ll ans = 0;
    while (d > 1)
    {
        while (a[d] == 1 && d > 1)
        {
            d--;
            ans = (ans + 1) % mod;
        }
        if (a[d] > 1 && d > 1)
        {
            ans++;
            ans = (ans + ans * (a[d] - 1)) % mod;
            d--;
        }
    }
    cout << ans;
    return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值