T1:数独
题意:有
5种操作,分别为插入,删除,查询,合并,输出。
解析:一道模拟题,注意细节就好了。
#include<bits/stdc++.h>
using namespace std;
int Read(){
int x=0;
char ch=getchar();
while(!isdigit(ch)){
ch=getchar();
}
while(isdigit(ch)){
x=(x<<3)+(x<<1)+ch-'0';
ch=getchar();
}
return x;
}
const int mp[10][10]={{0,0,0,0,0,0,0,0,0,0},
{0,1,1,1,2,2,2,3,3,3},
{0,1,1,1,2,2,2,3,3,3},
{0,1,1,1,2,2,2,3,3,3},
{0,4,4,4,5,5,5,6,6,6},
{0,4,4,4,5,5,5,6,6,6},
{0,4,4,4,5,5,5,6,6,6},
{0,7,7,7,8,8,8,9,9,9},
{0,7,7,7,8,8,8,9,9,9},
{0,7,7,7,8,8,8,9,9,9}};
struct Node{
int h[10],l[10],g[10];
int a[10][10];
void Clear(){
for(int i=1;i<=9;i++){
h[i]=l[i]=g[i]=0;
for(int j=1;j<=9;j++){
a[i][j]=0;
}
}
}
}f[105];
int cnt=0;
char s[105];
bool pd(int k,int x,int y,int i){
return (!(f[k].h[x]&(1<<i)))&&!((f[k].l[y]&(1<<i)))&&(!(f[k].g[mp[x][y]]&(1<<i)));
}
void Copy(){
++cnt;
for(int i=1;i<=9;i++){
f[cnt].h[i]=f[cnt-1].h[i];
f[cnt].l[i]=f[cnt-1].l[i];
f[cnt].g[i]=f[cnt-1].g[i];
for(int j=1;j<=9;j++){
f[cnt].a[i][j]=f[cnt-1].a[i][j];
}
}
}
void Re(){
for(int i=1;i<=9;i++){
f[cnt].h[i]=f[cnt].g[i]=f[cnt].l[i]=0;
}
for(int i=1;i<=9;i++){
for(int j=1;j<=9;j++){
f[cnt].h[i]|=(1<<f[cnt].a[i][j]);
f[cnt].l[j]|=(1<<f[cnt].a[i][j]);
f[cnt].g[mp[i][j]]|=(1<<f[cnt].a[i][j]);
}
}
}
void Ins(int x,int y,int k){
Copy();
if(f[cnt].a[x][y]){
printf("Error\n");
return ;
}
if(f[cnt].h[x]&(1<<k)){
printf("Error:row!\n");
return ;
}
if(f[cnt].l[y]&(1<<k)){
printf("Error:column!\n");
return ;
}
if(f[cnt].g[mp[x][y]]&(1<<k)){
printf("Error:square!\n");
return ;
}
printf("OK!\n");
f[cnt].h[x]|=(1<<k);
f[cnt].l[y]|=(1<<k);
f[cnt].g[mp[x][y]]|=(1<<k);
f[cnt].a[x][y]=k;
}
void Del(int x,int y){
Copy();
if(f[cnt].a[x][y]==0){
printf("Error!\n");
return ;
}
printf("OK!\n");
f[cnt].a[x][y]=0;
Re();
}
void query(int x,int y){
Copy();
if(f[cnt].a[x][y]){
printf("Error!\n");
return ;
}
int ans=0,Q[105];
for(int i=1;i<=9;i++){
if(pd(cnt,x,y,i)) Q[++ans]=i;
}
cout<<ans<<endl;
for(int i=1;i<=ans;i++){
printf("%d\n",Q[i]);
}
}
void Merge(int x,int y){
int ans1=0,ans2=0;
Copy();
f[cnt].Clear();
for(int i=1;i<=9;i++){
for(int j=1;j<=9;j++){
if(f[x].a[i][j]){
if(pd(cnt,i,j,f[x].a[i][j])){
f[cnt].a[i][j]=f[x].a[i][j];
f[cnt].h[i]|=(1<<f[x].a[i][j]);
f[cnt].l[j]|=(1<<f[x].a[i][j]);
f[cnt].g[mp[i][j]]|=(1<<f[x].a[i][j]);
ans1++;
continue;
}
}
if(f[y].a[i][j]){
if(pd(cnt,i,j,f[y].a[i][j])){
f[cnt].a[i][j]=f[y].a[i][j];
f[cnt].h[i]|=(1<<f[y].a[i][j]);
f[cnt].l[j]|=(1<<f[y].a[i][j]);
f[cnt].g[mp[i][j]]|=(1<<f[y].a[i][j]);
ans2++;
}
}
}
}
printf("%d %d\n",ans1,ans2);
}
void Print(){
Copy();
for(int i=1;i<=9;i++){
printf("+-+-+-+-+-+-+-+-+-+\n");
for(int j=1;j<=9;j++){
printf("|%d",f[cnt].a[i][j]);
}
printf("|\n");
}
printf("+-+-+-+-+-+-+-+-+-+\n");
}
int main(){
freopen("sudoku.in","r",stdin);
freopen("sudoku.out","w",stdout);
for(int i=1;i<=9;i++){
for(int j=1;j<=9;j++){
f[0].a[i][j]=Read();
f[0].h[i]|=(1<<f[0].a[i][j]);
f[0].l[j]|=(1<<f[0].a[i][j]);
f[0].g[mp[i][j]]|=(1<<f[0].a[i][j]);
}
}
int T=Read(),x,y,k;
while(T--){
scanf("%s",s);
if(s[0]=='I'){
x=Read(),y=Read(),k=Read();
Ins(x,y,k);
}
if(s[0]=='D'){
x=Read(),y=Read();
Del(x,y);
}
if(s[0]=='Q'){
x=Read(),y=Read();
query(x,y);
}
if(s[0]=='M'){
x=Read(),y=Read();
Merge(x,y);
}
if(s[0]=='P'){
Print();
}
}
fclose(stdin);
fclose(stdout);
}
没什么好说的,注意读题!!!
T2:分糖果(原皇后游戏)
传送门
解析:题目要求的是这样一个式子中
c[i]的最大值的最小值:
由于
ai,bi均为正数,所以
ci一定是递增的,故我们要求的即为
cn的最小值。
我们有一个结论:对于两位大臣
(ai,bi),(aj,bj),如果
min(ai,bj)≤min(aj,bi),那么我们将
(ai,bi)放在前面更优,证明如下(
2014年北京市高考理科数学第
20题第
2问):
c1=a1+b1
c2=max(c1,a1+a2)+b1=max(a1+b1,a1+a2)+b2
c2=max(a1+b1+b2,a1+a2+b2)
c3=max(c2,a1+a2+a3)+b3
c3=max(a1+b1+b2,a1+a2+b2,a1+a2+a3)+b3
c3=max(a1+b1+b2+b3,a1+a2+b2+b3,a1+a2+a3+b3)
综上,我们可以发现一个规律:
我们记
Sn(k)为
∑i=1kai+∑i=knbi
那么
ci=max(Si(1),Si(2),Si(3),...,Si(i))
但这只是我们找出来的规律,下面给出严谨证明:
我们考虑用一个
2∗n的矩阵来表示我们的序列
(a1,b1),(a2,b2),...,(an,bn)
以
c3为例,我们来画出这些走法,它们分别对应所有的
S:
⎣⎡a1↓b1→a2b2→a3b3⎦⎤
⎣⎡a1b1→a2↓b2→a3b3⎦⎤
⎣⎡a1b1→a2b2→a3↓b3⎦⎤
所以
cn在我们的矩阵里表示从
a1到
bn的
n条路径中
n+1个数字和的最大值。
我们用数学归纳法证明
ci=max(Si(1),Si(2),Si(3),...,Si(i)):
当
n=1时,
c1=max(S1(1)),显然成立。
假设当
n=k(k≥1)时,命题成立,即
ck=max(Sk(1),Sk(2),Sk(3),...,Sk(k))
那么当
n=k+1时,
ck+1=max(ck,a1+a2+...+ak+1)+bk+1
将
bk+1带入
max中
ck+1=max(ck+bk+1,a1+a2+...+ak+1+bk+1)
合并后面的项
ck+1=max(ck+bk+1,Sk+1(k+1))
将
ck拆开
ck+1=max(Sk(1)+bk+1,Sk(2)+bk+1,...,Sk(k)+bk+1,Sk+1(k+1))
按照
S的定义合并所有项
ck+1=max(Sk+1(1),Sk+1(2),...,Sk+1(k+1))
原命题得证。
下一步,我们来证明如果
min(ai,bj)≤min(aj,bi),那么我们将
(ai,bi)放在前面更优。
考虑上文提到的矩阵,我们对于一个矩阵
⎣⎡a1b1a2b2a3b3......anbn⎦⎤
我们如果调换两人的顺序,也就是调换矩阵中两列的顺序。
我们假设调换第
k列和第
k+1列,我们得到一个新矩阵
⎣⎡a1′b1′a2′b2′a3′b3′......an′bn′⎦⎤
其中
⎣⎡aibi⎦⎤=⎣⎡ai′bi′⎦⎤(1≤i≤n且i=k,i=k+1)
⎣⎡akbk⎦⎤=⎣⎡ak+1′bk+1′⎦⎤
⎣⎡ak+1bk+1⎦⎤=⎣⎡ak′bk′⎦⎤
我们记
Sn′(k)为
∑i=1kai′+∑i=knbi′,cn′=max(Sn′(1),Sn′(2),...,Sn′(n))
我们设
σ=Sn(k−1)=Sn′(k−1)=∑i=1k−1ai+∑i=k+2nbi=∑i=1k−1ai′+∑i=k+2nbi′,那么我们有:
Sn(k)=σ+ak+bk+bk+1
Sn(k+1)=σ+ak+ak+1+bk+1
Sn′(k)=σ+ak′+bk′+bk+1′=σ+ak+1+bk+1+bk
Sn′(k+1)=σ+ak′+ak+1′+bk+1′=σ+ak+1+ak+bk
记
m=min(ak,ak+1,bk,bk+1),M=max(Sn(k),Sn(k+1),Sn′(k),Sn′(k+1))
我们分别讨论
m的取值,
当
m=ak时,依照上文推出的
S,S′的表达式,
M=Sn′(k),所以
max(Sn′(k),Sn′(k+1))≥max(Sn(k),Sn(k+1)),即
cn′≥cn
其余三种情况同理。
最后我们得出当
m=ak或bk+1时,
cn≤cn′,所以不应交换。
当
m=ak+1或bk时,
cn≥cn′,此时应交换
k与k+1两列。
相邻两数间的情况可以方便的推广到任意两数交换的情况。
证毕。
从得到的式子
min(ai,bj)≤min(aj,bi)可以看出,我们只需按这个条件排序后模拟即可。
但是,我们这样做是不严谨的。考虑这样一组数据,
7 3
1 1
1 6
显然满足上式,答案输出是
17。
但是我们可以换一种排列方式也满足上式,
1 1
1 6
7 3
此时显然也满足,且答案为
12。
为什么会出现这种情况呢?
原因就是这个式子不满足传递性,交换一次这样的式子对后面确实不会产生什么影响,但如果多交换几次答案可能就会变。
我们考虑对于一些数,如果某一组数排在前面,那么其
ai必定越小越好,其
bi必定越大越好。
我们按照
a与b的大小关系将其分为
3组:
⎩⎪⎨⎪⎧ai<bi,aj<bj,按a升序排序①ai=bi,aj=bj,归入情况1中②ai>bi,aj>bj,按a降序排序③
由于题目中描述的式子是
ci=max(ci−1,∑i=1jaj)+bi,所以按照贪心的思路,我们先分情况在块内排序,再按照
①②③的顺序排序,之后模拟即可。
#include<bits/stdc++.h>
#define int long long
using namespace std;
struct Node{
int a,b,d;
}f[50005];
int c[50005];
bool cmp(Node x,Node y){
if(x.d!=y.d) return x.d<y.d;
if(x.d<=0) return x.a<y.a;
return x.b>y.b;
}
signed main(){
int T;
scanf("%lld",&T);
while(T--){
int n;
scanf("%lld",&n);
for(int i=1;i<=n;i++){
scanf("%lld%lld",&f[i].a,&f[i].b);
if(f[i].a>f[i].b) f[i].d=1;
if(f[i].a==f[i].b) f[i].d=0;
if(f[i].a<f[i].b) f[i].d=-1;
}
sort(f+1,f+n+1,cmp);
int s=0;
for(int i=1;i<=n;i++){
s+=f[i].a;
c[i]=max(c[i-1],s)+f[i].b;
}
cout<<c[n]<<endl;
}
}
T3:异或(原大新闻)
传送门
题意简述
有两个数
x,y,其中
x是随机生成的
[0,n)间一个正整数,
y有
p的概率为
[0,n)间一个数,使得
x xor y最大,有
(1−p)的概率生成方式同
x,求
x xor y的期望值。
解题思路
我们先考虑暴力的解法。
35pts
n≤100,随便怎么暴力都可以过。
当
p=0时,即求
n2∑i=1n∑j=1ni xor j
直接两重循环求解即可,时间复杂度
O(n2),记得开
double
当
p=1时,我们只需对上述情况的第二层循环做出微调,将求和改为求最大值,再讲每一个
i所对应的最大值分别相加即可。
代码略。
50pts
我们可以观察到后面
15pts的数据是
2k的形式,那么它们间一定有着一些特殊规律。
经打表检验得:
当
p=0时,答案为
2n−1
当
p=1时,答案为
n−1
下面给出证明:
首先,在
p=1时,对于每个数
x,总有一个数
y∈[0,n)使得
x xor y=n−1,所以期望为
nn(n−1)=n−1
在
p=0时,我们首先有一个结论:
x xor i+x xor (2k−i−1)=2k−1i∈[0,2k)
证明也很简单,手玩一下就好了。
所以我们对
[0,n)间数两两配对,求得期望为
n2n×2n×(n−1)=2n−1
100pts
由于这里有
0≤p≤1的情况,所以我们要先解决这种情况。
设
P为总的期望值,
P1为
p取
0时的期望,
P2为
p取
1时的期望,
由期望的一些基本知识可以很容易的推出
P=(1−p)×P1+p×P2
那么下面就是如何计算
P1,P2的问题了。
先讨论
p=0时的情况,
我们设对于两个数异或起来的值,第
i位为
1为事件
A,第
j位为
1为事件
B,由位运算的性质知
A,B相互独立,故我们可以分开计算。
我们再设从
[0,n)中选出一个数,其二进制第
i位为
1的概率为
pi,那么刚才的答案就是
i=0∑logn2×pi×(1−pi)×2i
考虑对于区间
[0,2k),一定有区间
[0,2k−1)的所有数的第
k位均为
0,区间
[2k−1,2k)的所有数第
k位均为
1。
然后我们考虑区间
[0,n),那么必定有区间
[S×2k,S×2k+2k−1)中的数第
k位为0,区间
[S×2k+2k−1,(S+1)×2k+1)中数的第
k位为0,所以第
k位为1的数的个数是:
⌊2k+1n⌋×2k+max(n mod 2k+1−2k,0)
故概率
pi为
n⌊2k+1n⌋×2k+max(n mod 2k+1−2k,0)
时间复杂度
O(logn)
我们再来考虑
p=1时的情况(比较毒瘤)
我们设
f(x)为
[0,n)内使
x xor f(x)最大的
f(x)的值
如果没有范围的限制的话,
f(x)应为
x按位取反后的值,现在多了一个
n的限制,那我们可以考虑用一种贪心的手法保留高位的
1,如果某一位取
1会使
f(x)≥n,那么这一位就只能取
0。
我们考虑最高的
i−1位
(i−1≥0)和
n−1的前
(i−1)位相同的所有的
x对答案的贡献,我们考虑
n−1与
x的第
i位,有下列情况:
1.n−1的第
i位为
0,由于
x≤n−1,f(x)≤n−1,所以
x的第
i位和
f(x)的第
i位必须为
0。
2.n−1的第
i位为
1,那么
x第
i位的取值又可以分两种情况:
①
:x的第
i位为
1,那么
f(x)的第
i位为
0,且以后的位数一定可以取
x取反后的值。
②
:x的第
i位为
0,那么
f(x)的第
i位为
1,但还有后面的限制。
每次处理时
n−1的规模将减半,故时间复杂度为
O(logn)
Code
#include<bits/stdc++.h>
#define ll long long
using namespace std;
double solve1(ll n){
double ret=0;
ll Pow=0,tmp=n-1;
while(tmp!=0){
Pow++;
tmp>>=1;
}
for(int i=Pow;i>0;i--){
ll nw=(n>>i)*(1LL<<i-1)+min(n-(n>>i<<i),1LL<<i-1);
double p=double(n-nw)/n;
ret+=(1.0-p)*(1LL<<(i-1))*p;
}
return ret*2.0;
}
double solve2(ll n){
if(n==1) return 0.0;
double ret=0.0;
ll v=1LL,delta,num,tmp=n-1;
n--;
while(v<=tmp){
v<<=1;
}
delta=v-1LL;
v>>=1;
ret+=(double)delta*(n-v+1);
ret+=(double)v*v;
num=v,delta>>=1;
while(v!=1){
v>>=1,delta>>=1;
if(n&v){
ret+=(double)num*v;
ret+=(double)(num>>1)*delta;
num>>=1;
}
else
ret+=(double)(num>>1)*v;
}
return ret/(double)(n+1);
}
int main(){
ll n;
double p;
scanf("%lld%lf",&n,&p);
double p1=solve1(n),p2=solve2(n),ans=(1.0-p)*p1+p*p2;
printf("%.6lf\n",ans);
}