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;
}