noip前冲刺阶段的机房模拟......随便搞搞
T1(签到题)
就随便看着1e9的数据..........什么O(n)、O(nlog(n)) 之类的数据就别想了吧.........
我们一看到xor,就应该可以马上反应过来其实可以拆位来做,因为一个数二进制下每一位对于答案的贡献是完全独立的,可以直接相加。
我们对于一位来说,肯定是只有0和1这两种位数,那么我们考虑何种情况下对答案是有贡献的。
首先,一对1和一对0是肯定没有贡献的,那么我们考虑一个1和一个0;
于是我们可以得到:假设这个数的这一位是1,那么肯定需要一个0才能使得这一个1对答案有贡献。
那么贡献是多少呢?
我们可以轻松得到0的个数*1的个数*(1<<i)*2
为什么要乘以2呢?
因为如果说1和0有贡献的话,0对1同样是有贡献的,所以我们计算两次就可以防止遗漏
接着我们考虑该如何统计每一位上[L,R]之间的1的个数
我们发现如果要从0开始数数,0、1、2、3、4、5……会出现以下情形
0000
0001
0010
0011
0100
0101
0110
0111
……
我们发现从低到高的话(从右向左),第一位的循环节长度是2(01 01 01)第二位是4(0011 0011 0011),第三位是8(00001111 00001111 00001111,而其中1、0个掺半。
如果我们直接根据L、R来考虑0、1的个数的话会非常的复杂,因为你不知道对于L来说到底处于当前循环节的位置。,R也同理
但是如果说我们使用1~R间的个数减去1~L-1的个数的话,由于0肯定是一个任意一位的循环节的开始位置,那么我们只需要处理R就好了
R就随便取下模就OK了
代码
#include<cstdio>
#define OP "xor"
#define LL long long
const int MOD=1e9+7;
int main()
{
std::freopen(OP".in","r",stdin);
std::freopen(OP".out","w",stdout);
int T;
std::scanf("%d",&T);
while(T--)
{
int l,r;
std::scanf("%d%d",&l,&r);
LL ans=0;
l--;
for(int i=0;(1<<i)<=r;i++)
{
int zl=(l+1)/(1<<i+1);
int zr=(r+1)/(1<<i+1);
int num1_l;
int num1_r;
int num0;
int num1;
if((l+1)%(1<<i+1)>(1<<i))
{
num1_l=(1<<i)*zl+(l+1)%(1<<i+1)-(1<<i);
}
else
{
num1_l=(1<<i)*zl;
}
if((r+1)%(1<<i+1)>(1<<i))
{
num1_r=(1<<i)*zr+(r+1)%(1<<i+1)-(1<<i);
}
else
{
num1_r=(1<<i)*zr;
}
num1=num1_r-num1_l;
num0=(r-l)-num1;
ans=(ans+2ll*num0*num1%MOD*(1<<i)%MOD)%MOD;
}
std::printf("%lld\n",ans);
}
return 0;
}
/*
2
1 2
0 1023
1
1 2
*/
T2(又一次超纲考博弈论........)
首先要搞清楚,这一道题虽然看上去和NIM游戏是如此的相似
但是并不一样.............
我们首先考虑简单的情况 (也就是部分分)
第一,当x、y、z很小的时候,我们可以考虑使用非常简单的博弈论基础内容,开一个三维数组来记录当前的x、y、z是必胜态或者是必败态,然后用必胜必败态的基础性质来转移(一个状态为必胜态当且仅当当前状态可以转移到一个必败态,一个状态为必败态当且仅当它能够转移到的所有状态均为必胜态)
这样的话时间复杂度为n^4
我们再来考虑只有两堆的情况(著名的威佐夫博弈)。
根据之前的做法打表,我们可以发现只有两堆的情况有一些特殊的性质,打个比方,我们打出只有两堆的情况时侯的前五项必败态,他们分别是
1 2 0
3 5 0
4 7 0
6 10 0
8 13 0
……
我们可以发现它们拥有的两个性质:
1、对于任意一个自然数,存在且仅存在一个自然数使得当前情况为必败态
2、对于任意一个必败态,其两堆石头的差值一定与任意必败态不同。
这两个性质都非常好证明
1、若对于自然数X,存在Y1,Y2使得其为必败态,我们假设Y1<Y2,则有:
X、Y2可以通过先手的操作使它变为X、Y1。
X、Y1为必败态,则后手必败,先手必胜,与X、Y2必败矛盾。
故对于任意一个自然数,存在且仅存在一个自然数使得当前情况为必败态。
2、对于自然数对(X1、Y1);(X2、Y2),两者均为必败态 ,且X1<X2,Y1<=Y2,且abs(x1-x2)==abs(y1-y2),则有
先手拿到X2、Y2状态
先手通过操作使其变为X1、Y1
X1、Y1必败,则后手必败,先手必胜,与X2、Y2必败矛盾
故对于任意一个必败态,其两堆石头的差值一定与任意必败态不同。
至此证毕
两者的证明其实真正依赖的定理都是博弈论的基础性质:一个必败态只能转移到必胜态,不可能转移到必败态,否则与它是必败态完全矛盾。
知道了这一个性质,当只有两堆棋子的时候我们就可以直接n^2for循环得到任意的值对应的必败态,如果另外的一个值是其对应的必败态的值则就输出No,否则输出Yes
至此,我们拥有了这道题目的70pts算法。
而实质意义上,两堆的情况稍作推理就可以转移至三堆的情况
对于已经确定的前两堆石子(a,b),存在且仅存在一个c使得这个三元组为必败态,其余均为必胜态
然后我们可以发现,对于同一个c,a、b之间的差值仍然不变。
于是我们定于f[a][b],其含义为当前两堆石头为a,b时所对应的唯一的使得当前状态成为必败态的第三堆石头
那么我们考虑从小到大枚举C
我们可以发现,对于C来说,由于从小到大枚举,那么f[a][b]=k且k<C的(A、B)是已经确定了的。
根据以上性质,我们可以得到:当存在f[a][b]=k且k<C的时候,有:f[a+c-k][b]!=c,f[a][b+c-k]!=c,f[a+c-k][b+c-k]!=c
所以我们记录一下这些特殊的必定不等的值,再判断一下a、b出现次数是否大于1以及他们的差值是否只出现一次,就可以得到a、b了
然后输入x、y、z,直接判断f[x][y]是否等于z,如果等于输出No(必败),否则输出Yes;
#include<cstdio>
#include<cstring>
#include<algorithm>
#define OP "stone"
const int MAXN=305;
int f[MAXN][MAXN];
int add[MAXN][MAXN];
int dif[MAXN];
int cnt[MAXN];
int Abs(int x)
{
return x>=0?x:-x;
}
int main()
{
std::freopen(OP".in","r",stdin);
std::freopen(OP".out","w",stdout);
std::memset(f,63,sizeof f);
int tag=0;
int T;
std::scanf("%d",&T);
for(int i=0;i<=300;i++)
{
tag++;
for(int j=0;j<=300;j++)
{
for(int k=0;k<=300;k++)
{
if(f[j][k]<i)
{
int delta=i-f[j][k];
if(j+delta<MAXN)
{
add[j+delta][k]=tag;
}
if(k+delta<MAXN)
{
add[j][k+delta]=tag;
}
if(std::max(j,k)+delta<MAXN )
{
add[j+delta][k+delta]=tag;
}
}
else
if(std::max(std::max(cnt[j],cnt[k]),dif[Abs(j-k)])<tag&&add[j][k]<tag)
{
f[j][k]=f[k][j]=i;
cnt[j]=cnt[k]=tag;
dif[Abs(j-k)]=tag;
}
}
}
}
while(T--)
{
int x,y,z;
std::scanf("%d%d%d",&x,&y,&z);
f[x][y]==z?std::printf("No\n"):std::printf("Yes\n");
}
return 0;
}
/*
3
1 1 1
1 2 0
3 2 2
*/
如果你完美地看出了性质,这本来是不是很难的题目.....
但是性质贼TM难想,属于不知道就寸步难行(出题人原话)的那种
区间贡献的系数只有2、0、-2三种,根本不用考虑到底放在具体哪个区间,只需要考虑放在哪种区间即可.........
知道这个性质之后真的随随便便都能过............
#include<cstdio>
#include<cstring>
#include<algorithm>
#define OP "optimization"
const int MAXN=3e4+5;
const int INF=1e9+7;
const int KN=205;
int dp[MAXN][KN][4];
int a[MAXN];
int main()
{
std::freopen(OP".in","r",stdin);
std::freopen(OP".out","w",stdout);
for(int i=1;i<KN;i++)
{
for(int j=0;j<4;j++)
{
dp[0][i][j]=-INF;
}
}
int n,k;
std::scanf("%d%d",&n,&k);
for(int i=1;i<=n;i++)
{
std::scanf ("%d",a+i);
}
for(int i=1;i<=n;i++)
{
for(int j=1;j<=k;j++)
{
int flag=2-(j==1||j==k);
dp[i][j][0]=std::max(dp[i-1][j][0],dp[i-1][j-1][3])-flag*a[i];
dp[i][j][1]=std::max(dp[i][j][0],dp[i-1][j][1]);
dp[i][j][2]=std::max(dp[i-1][j-1][1],dp[i-1][j][2])+flag*a[i];
dp[i][j][3]=std::max(dp[i-1][j][3],dp[i][j][2]);
if(flag-1)
{
dp[i][j][1]=std::max(dp[i][j][1],dp[i-1][j-1][1]);
dp[i][j][3]=std::max(dp[i][j][3],dp[i-1][j-1][3]);
}
}
}
std::printf("%d\n",std::max(dp[n][k][1],dp[n][k][3]));
}
/*
5 3
5 2 4 3 1
*/