B. Random Nim Game 结论,签到
D. Medians Strike Back 构造,打表
H. HEX-A-GONE Trails 博弈论,ST表
K. Three Operations 贪心,签到
M. Minimal and Maximal XOR Sum 思维,构造,逆序对
打表发现a1>1的时候,概率总是1/2。随后,一旦有ai>1,概率也是1/2。除了全为1的情况特判,其余都是1/2.
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int main()
{
int t;
scanf("%d", &t);
while (t--)
{
ll p0 = 1, p1 = 0;
int n;
scanf("%d", &n);
bool ext = 0;
for (int i = 0; i < n; i++)
{
int x;
scanf("%d", &x);
if (x > 1)
{
ext = 1;
}
}
if (ext)
{
puts("499122177");
}
else if (n % 2)
{
puts("1");
}
else
{
puts("0");
}
}
}
虽然是可以构造的,但是构造起来难度不小,打表暴力搜索外加剪枝, 会发现,每段答案的起始点,奇数时是n*(n+1)-1,否则是n*n。所以直接二分即可。
/*#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int a[100005], n;
int tr[100005];
int lowbit(int now)
{
return now & (-now);
}
void add(int now)
{
for (int i = now; i <= n; i += lowbit(i))
{
tr[i]++;
tr[i] %= 2;
}
}
int qry(int now)
{
int res = 0;
for (int i = now; i > 0; i -= lowbit(i))
{
res += tr[i];
res %= 2;
}
return res;
}
int main()
{
int t;
scanf("%d", &t);
while (t--)
{
scanf("%d", &n);
for (int i = 1; i <= n; i++)
scanf("%d", &a[i]);
if (n == 1)
{
cout << 0 << " " << 1 << "\n";
continue;
}
int ans = 0;
for (int i = 1; i <= n; i++)
{
ans += qry(n) - qry(a[i]);
ans = (ans + 2) % 2;
add(a[i]);
}
int mx = 0, idx;
for (int i = 29; i >= 0; i--)
{
if ((n >> i) & 1)
{
idx = i;
break;
}
}
for (int i = idx; i >= 0; i--)
{
mx |= (1 << i);
}
// cout << "mx:" << mx << "\n";
if (ans & 1)
{
cout << 2 << " " << mx << "\n";
}
else
{
mx -= 2;
cout << 0 << " " << mx << "\n";
}
for (int i = 0; i <= n; i++)
{
tr[i] = 0;
}
}
return 0;
}*/
#include <bits/stdc++.h>
using namespace std;
typedef long long int ll;
int main()
{
int t;
cin >> t;
while (t--)
{
ll n;
scanf("%lld", &n);
if (n <= 3)
{
cout << 1 << '\n';
}
else if (n <= 10)
{
cout << 2 << '\n';
}
else
{
ll l = 1, r = 1e9;
while (l < r)
{
ll mid = (l + r + 1) >> 1;
ll sum = 0;
if (mid & 1)
{
sum = mid * (mid + 1) - 1;
}
else
{
sum = mid * mid;
}
if (sum > n)
r = mid - 1;
else
l = mid;
}
cout << l << "\n";
}
}
return 0;
}
有这样一种策略,先手进入非ST->ED路径上的子树,有了ST的阻隔,先手会选择这些路径中最长的,同样因为ED不能进入ST的路径,所以只能选择一条最长的路径,这条路径可以是ED内部,也可以是先走到ST-ED路径某点,再进入其内部。如果二者路径前者大于后者,显然是一个必胜策略。否则,先手必败,则不会选择这样走,而是会直接走到图中虚线路径上,于是局面又回到刚开始的状态,只不过先手变成ED。
有了这一策略,可以预处理出图中虚线路径,每个路径点向非路径点延伸的最长路径长度。由于有了虚线路径的阻隔,这些每个点的最长路径都是独立的,互不影响。
以当前先手为例,我们需要查询len[st]的大小,以及ed到st右侧虚点路径上,每个点的最长路径+该点距离ed的长度(pos-ed) ,所以可以将len[pos]+pos加入St表或者其他区间最值数据结构进行区间查询。
#include <bits/stdc++.h>
using namespace std;
int maxx1[100000+10][31],maxx2[100000+10][31];
vector<int>v[100000+10];
int pre[100000+10],temp[100000+10],len,book[100000+10];
void dfs(int now,int fa)
{
pre[now]=fa;
for(auto it:v[now])
{
if(it==fa)
continue;
dfs(it,now);
}
}
int maxlen[100000+10];
int getlen(int now,int fa)
{
int maxx=0;
for(auto it:v[now])
{
if(it==fa||book[it])
continue;
maxx=max(maxx,getlen(it,now)+1);
}
return maxx;
}
int lo[100000+10];
int getmax(int l,int r,int flag)
{
if(flag==1)
{
int lo2=lo[r-l+1];
return max(maxx1[l][lo2],maxx1[r-(1<<lo2)+1][lo2]);
}
else
{
int lo2=lo[r-l+1];
return max(maxx2[l][lo2],maxx2[r-(1<<lo2)+1][lo2]);
}
}
int main()
{
cin.tie(0);
ios::sync_with_stdio(0);
int t;
cin>>t;
while(t--)
{
int n;
cin>>n;
len=0;
int st,ed;
cin>>st>>ed;
lo[0]=-1;
for(int i=1;i<=n;i++)
{
v[i].clear();
book[i]=0;
pre[i]=0;
maxlen[i]=0;
lo[i]=lo[i/2]+1;
}
for(int i=1;i<n;i++)
{
int x,y;
cin>>x>>y;
v[x].push_back(y);
v[y].push_back(x);
}
for(int i=0;i<=n;i++)
{
for(int j=0;j<=30;j++)
{
maxx1[i][j]=maxx2[i][j]=0;
}
}
dfs(st,0);
int now=ed;
while(now)
{
len++;
temp[len]=now;
book[now]=1;
now=pre[now];
}
for(int i=1;i<=len;i++)
{
maxlen[i]=getlen(temp[i],0);
maxx1[i][0]=maxlen[i]+i;
maxx2[i][0]=maxlen[i]-i;
}
for(int j=1;j<=19;j++)
{
for(int i=1;i+(1<<j)-1<=n;i++)
{
maxx1[i][j]=max(maxx1[i][j-1],maxx1[i+(1<<(j-1))][j-1]);
maxx2[i][j]=max(maxx2[i][j-1],maxx2[i+(1<<(j-1))][j-1]);
}
}
int op1=len,op2=1,flag=0;
while(1)
{
if(flag==0) //当前是
{
if(op1-1==op2)
{
if(maxlen[op1]>maxlen[op2])
{
cout<<1<<'\n';
break;
}
else
{
cout<<0<<'\n';
break;
}
}
if(maxlen[op1]>getmax(op2,op1-1,1)-op2)
{
cout<<1<<'\n';
break;
}
op1--;
}
else
{
if(op2+1==op1)
{
if(maxlen[op2]>maxlen[op1])
{
cout<<0<<'\n';
break;
}
else
{
cout<<1<<'\n';
break;
}
}
if(maxlen[op2]>getmax(op2+1,op1,2)+op1)
{
cout<<0<<'\n';
break;
}
op2++;
}
flag^=1;
}
}
return 0;
}
直接贪心取即可,注意不能用二三操作时,要直接退出,否则超时。
首先,每次交换一个相邻的,交换总次数为逆序对数,则逆序对数为0时,最小的答案为0,否则是2。 否则,考虑构造一种填满n的二进制位数的方案。即每次交换长度为2的若干次幂的操作。每次交换2的次幂长度时,长度为2,奇偶性改变,4的时候,逆序对数由x转化为4*(4-1)/2=6-x,因为6是偶数,故奇偶性不变,又因为4之后长度皆为2的次幂,每次n*(n-1)/2都是偶数,故只有2的时候才会改变。所以得出结论,当逆序对位偶数时,最大为后一位扣除2,否则不扣除。特别的,因为长度为1的翻转一定可以,故最后1位一定取。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int a[100005], n;
int tr[100005];
int lowbit(int now)
{
return now & (-now);
}
void add(int now)
{
for (int i = now; i <= n; i += lowbit(i))
{
tr[i]++;
tr[i] %= 2;
}
}
int qry(int now)
{
int res = 0;
for (int i = now; i > 0; i -= lowbit(i))
{
res += tr[i];
res %= 2;
}
return res;
}
int main()
{
int t;
scanf("%d", &t);
while (t--)
{
scanf("%d", &n);
for (int i = 1; i <= n; i++)
scanf("%d", &a[i]);
if (n == 1)
{
cout << 0 << " " << 1 << "\n";
continue;
}
int ans = 0;
for (int i = 1; i <= n; i++)
{
ans += qry(n) - qry(a[i]);
ans = (ans + 2) % 2;
add(a[i]);
}
int mx = 0, idx;
for (int i = 29; i >= 0; i--)
{
if ((n >> i) & 1)
{
idx = i;
break;
}
}
for (int i = idx; i >= 0; i--)
{
mx |= (1 << i);
}
if (ans & 1)
{
cout << 2 << " " << mx << "\n";
}
else
{
mx -= 2;
cout << 0 << " " << mx << "\n";
}
for (int i = 0; i <= n; i++)
{
tr[i] = 0;
}
}
return 0;
}