比赛地址:https://www.jisuanke.com/contest/1224(已添加到计蒜客题库)
博弈论+线段树
博弈论是最经典的Nim博弈,用线段树维护区间sg函数的异或值,然后区间更新就可以做。注意要预处理出每个子游戏的sg函数值。
代码:
#include
#include
#include
#include
#include
#include
using namespace std;
const int maxn = 1000005;
int sg[1005];
int k;
int tree[maxn*8];
int lazy[maxn*8];
void push_down(int n,int l,int r)
{
lazy[n*2]=lazy[n*2+1]=lazy[n];
int mid=(l+r)/2;
if((mid-l+1)%2)
tree[n*2]=sg[lazy[n]];
else
tree[n*2]=0;
if((r-mid)%2)
tree[n*2+1]=sg[lazy[n]];
else
tree[n*2+1]=0;
lazy[n]=0;
}
void build_tree(int n,int l,int r)
{
if(l==r){
int temp;
scanf("%d",&temp);
tree[n]=sg[temp];
}
else
{
int mid=(l+r)/2;
build_tree(n*2,l,mid);
build_tree(n*2+1,mid+1,r);
tree[n]=tree[n*2]^tree[n*2+1];
}
}
int query(int n,int l,int r,int ll,int rr)
{
if(l>=ll&&r<=rr)
{
return tree[n];
}
else
{
if(lazy[n])
push_down(n,l,r);
int mid=(l+r)/2;
int t1=0,t2=0;
if(mid>=ll)
t1=query(n*2,l,mid,ll,rr);
if(mid+1<=rr)
t2=query(n*2+1,mid+1,r,ll,rr);
return t1^t2;
}
}
void update(int n,int l,int r,int ll,int rr,int res)
{
if(l>=ll&&r<=rr)
{
lazy[n]=res;
if((r-l+1)%2)
tree[n]=sg[res];
else
tree[n]=0;
}
else
{
if(lazy[n])
push_down(n,l,r);
int mid=(l+r)/2;
if(mid>=ll)
update(n*2,l,mid,ll,rr,res);
if(mid+1<=rr)
update(n*2+1,mid+1,r,ll,rr,res);
tree[n]=tree[n*2]^tree[n*2+1];
}
}
int get_sg(int x)
{
int a[1005];
if(x<0)
return 1004;
if(x==0)
return sg[x]=0;
if(sg[x]!=-1)
return sg[x];
memset(a,0,sizeof(a));
for(int i=1;i<=k;i++)
{
a[get_sg(x-i)]=1;
}
for(int i=0;i<=1000;i++)
{
if(a[i]==0)
return sg[x]=i;
}
}
int main()
{
//freopen("test6.in","r",stdin);
//freopen("test6.output","w",stdout);
int n,m;
scanf("%d%d%d",&n,&m,&k);
memset(sg,-1,sizeof(sg));
for(int i=0;i<=1000;i++)
get_sg(i);
build_tree(1,1,n);
for(int i=1;i<=m;i++)
{
int l,r,h;
scanf("%d%d%d",&l,&r,&h);
int ans=query(1,1,n,l,r);
if(ans==0)
printf("No\n");
else
printf("Yes\n");
update(1,1,n,l,r,h);
}
return 0;
}
水题,数据也很水,怎么暴力都能过。
代码:
#include
#include
#include
#include
#include
using namespace std;
int mon[13]={0,31,28,31,30,31,30,31,31,30,31,30,31};
bool f(int x)
{
if(x%4==0)
{
if(x%100==0)
if(x%400==0)
return true;
else
return false;
else
return true;
}
else
return false;
}
int main()
{
int t;
scanf("%d",&t);
while(t--)
{
int y,m,d;
scanf("%d%d%d",&y,&m,&d);
int a=2017,b=8;
if(y==2017&&m==8)
{
printf("%d\n",d-16);
continue;
}
int ans=-16;
while(aif(b==2&&f(a))
ans++;
b++;
if(b==13)
{
a++;
b=1;
}
}
ans+=d;
printf("%d\n",ans);
}
return 0;
}
题目大意:给出n个字符串,每个字符串有一个权值,要求输出每个字符串在n个字符串中出现的总次数,以及每个字符串所有出现位置所在字符串的权值中,最大值与最小值。
考虑对于n各字符串建立AC自动机,将fail边反向,将会得到一棵fail树。正向fail边指向的是所在节点表示的字符串的最长后缀,因此,反向后的每个节点的fail边指向的是以该节点表示的字符串为后缀的字符串。可以得出,每个节点在fail树上的子孙个数就是这个节点所代表的字符串在自动机上出现的次数,建立自动机时保存单词的最后一个字母所在节点编号,之后对fail树进行dfs,统计每个节点的儿子个数,即可求出答案,因为一个节点可能被多个字符串包含,插入单词时要保存每个节点使用了多少次。
对于权值,建立自动机时保存每个节点对应的字符串的最大及最小权值,建立fail树后,查询每个词尾节点在fail树上的子孙节点的最大及最小权值,结果即为答案。可进行一遍dfs预处理出答案。
代码:
#include
#include
using namespace std;
const int maxn=1000005;
char s[1000000];
int ma[maxn],mi[maxn];
int net[maxn][26],fail[maxn],ed[maxn];
vector<int>ftree[maxn];
int root,L;
int maxnum[maxn][21];
int minnum[maxn][21];
int inc[maxn];
int node[maxn];
void RMQ(int n)
{
int i, j;
for(j = 1; j < 20; ++j)
for(i = 1; i <= n; ++i)
if(i + (1 << j) -1 <= n)
{
maxnum[i][j] = max(maxnum[i][j-1], maxnum[i+(1 << (j-1))][j-1]);
minnum[i][j] = min(minnum[i][j-1], minnum[i+(1 << (j-1))][j-1]);
}
}
int querymax(int s,int e)
{
int k = (int)((log(e-s+1))/log(2.0));
return max(maxnum[s][k], maxnum[e-(1 << k)+1][k]);
}
int querymin(int s,int e)
{
int k = (int)((log(e-s+1))/log(2.0));
return min(minnum[s][k], minnum[e-(1 << k)+1][k]);
}
int newnode()
{
for(int i = 0;i < 26;i++)
net[L][i] = -1;
ma[L]=0;
mi[L]=maxn;
inc[L]=0;
L++;
return L-1;
}
void init()
{
L = 0;
root = newnode();
}
void insert(char buf[],int id,int num)
{
int len = strlen(buf);
int now = root;
for(int i = 0;i < len;i++)
{
if(net[now][buf[i]-'a'] == -1)
net[now][buf[i]-'a'] = newnode();
now = net[now][buf[i]-'a'];
ma[now]=max(ma[now],id);
mi[now]=min(mi[now],id);
inc[now]++;
}
ed[num]=now;
}
void build()
{
queue<int>Q;
fail[root] = root;
for(int i = 0;i < 26;i++)
if(net[root][i] == -1)
net[root][i] = root;
else
{
fail[net[root][i]] = root;
ftree[root].push_back(net[root][i]);
Q.push(net[root][i]);
}
while( !Q.empty() )
{
int now = Q.front();
Q.pop();
for(int i = 0;i < 26;i++)
if(net[now][i] == -1)
net[now][i] = net[fail[now]][i];
else
{
fail[net[now][i]]=net[fail[now]][i];
ftree[net[fail[now]][i]].push_back(net[now][i]);
Q.push(net[now][i]);
}
}
}
int num=0;
int son[maxn];
int in[maxn];
int dfs(int id)
{
int l=ftree[id].size();
son[id]=inc[id];
for(int i=0;ireturn son[id];
}
int dfs2(int id)
{
int l=ftree[id].size();
in[id]=num;
node[id]=1;
maxnum[num][0]=ma[id];
minnum[num][0]=mi[id];
num++;
for(int i=0;ireturn node[id];
}
void query(int id)
{
int now=ed[id];
int ans1=querymin(in[now],in[now]+node[now]-1);
int ans2=querymax(in[now],in[now]+node[now]-1);
printf("%d %d %d\n",son[now],ans1,ans2);
}
void ppp()
{
for(int i=1;iprintf("%d %d\n",i,minnum[i][0]);
printf("ans==%d\n",querymin(15,16));
}
int main()
{
int n;
int temp;
cin>>n;
init();
for(int i=1;i<=n;i++)
{
scanf("%s%d",s,&temp);
insert(s,temp,i);
}
build();
dfs(root);
dfs2(root);
RMQ(L-1);
// ppp();
for(int i=1;i<=n;i++)
{
query(i);
}
return 0;
}
=
设:
根据两函数定义可知:
所以由莫比乌斯反演显然可得:
所以问题就转化成了求解 p(1)=∑min(n,m)i=1u(i)q(i) p ( 1 ) = ∑ i = 1 m i n ( n , m ) u ( i ) q ( i ) 的值。
进一步化简我们可以发现:
r(x,y) r ( x , y ) 显然可以 O(1) O ( 1 ) 时间求解,所以:
所以预处理出 u(i)∗i2 u ( i ) ∗ i 2 的前缀和就可以通过把 r(⌊ni⌋,⌊mi⌋) r ( ⌊ n i ⌋ , ⌊ m i ⌋ ) 合并同类项,来使得每次查询的时间复杂度降为 O(n−−√) O ( n )
代码:
#include
using namespace std;
#define maxn 1100052
#define MOD 1000000007
#define mod(x) ((x)%MOD)
long long int mu[maxn],sum_u[maxn];
long long int h(long long int x,long long int y)
{
long long int ret=0;
ret=mod( mod( (y+1)*y ) * mod( (x+1)*x/2 ) );
ret=mod( ret+ mod(y*mod(x*(x+1)*(2*x+1)/6)));
ret=mod( ret+ mod(x*mod(y*(y+1)*(2*y+1)/6)));
return ret;
}
long long int f(long long int x,long long int y)
{
long long int ret=0;
for(int i=1;i<=x;i++)
{
for(int j=1;j<=y;j++)
{
if(__gcd(i,j)==1)ret=mod(ret+(i+j)*(i+j));
}
}
return ret;
}
long long int prime[maxn/10];
bool vis[maxn];
void Init_mu()//初始化莫比乌斯函数
{
int cnt=0;
mu[1]=1;
for(long long int i=2;iif(!vis[i])
{
prime[cnt++]=i;
mu[i]=-1;
}
long long int j;
for(j=0;j1;
if(i%prime[j]==0){mu[i*prime[j]]=0;break;}
else mu[i*prime[j]]=-mu[i];
}
}
}
void init()
{
Init_mu();
sum_u[0]=0;
long long int i;
for(i=1;imod(sum_u[i-1]+mod(mu[i]*i*i));
}
int main()
{
int T;
long long int n,m,ans;
init();
scanf("%d",&T);
while(T--)
{
scanf("%lld%lld",&n,&m);
if(nlong long int t=n;n=m;m=t;}
n/=2018;m/=2018;
ans=0;
long long int i,last;
for(i=1,last=1;i<=m;i=last+1)
{
last=min(n/(n/i),(m/(m/i)));
ans=mod(ans+ (sum_u[last]-sum_u[i-1])*h(n/i,m/i) );
}
for(;i<=n;i=last+1)
{
last=(n/(n/i));
ans=mod(ans+ mod(sum_u[last]-sum_u[i-1])*h(n/i,0) );
}
printf("%lld\n",mod(mod(ans+MOD)*2018*2018));
}
}
该数列在OEIS上面的标号为:A185456
简单做法可以是二分答案,即位置数量,对于每个二分出来的答案,记忆化递归计算能容纳多少个人(因为递归时座位数量是以接近二分的速度减小的,所以时间也不会很多)。时间复杂度 O(logn) O ( l o g n )
代码:
#include
using namespace std;
long long int n,ans;
map<long long int,long long int>dp;
long long int f(long long int x)
{
if(x<=2)return 0;
if(dp.count(x)!=0)return dp[x];
if(x&1)
{
return dp[x]=2*f(x/2)+1;
}
else
{
return dp[x]=f(x/2)+f(x/2-1)+1;
}
}
long long int fast_pow(long long int x,long long int k)
{
long long int ret=1;
while(k>0)
{
if(k&1)ret*=x;
x*=x;
k>>=1;
}
return ret;
}
int main()
{
while(scanf("%lld",&n)!=EOF)
{
long long int ans=n+ fast_pow(2,1+floor(log2((double)(n-2))));
if(n==1)
{
printf("1\n");
continue;
}
if(n==2)
{
printf("3\n");
continue;
}
long long int l=3,r=1e17,mid;
while(l<=r)
{
mid=(l+r)/2;
if(f(mid-2)+2>=n)r=mid-1;
else l=mid+1;
}
printf("%lld\n",ans);
}
}
结论是:如果nm同奇偶,那么就算不能完全覆盖,如果nm不同奇偶,就可以完全覆盖。
下面证明:
1.如果nm都为奇数,那么巧克力的块数也为奇数,没办法分割成 1 * 2 的小块。
2.如果nm为一奇一偶,那么可以假设n为奇数,则巧克力可以分成2个 1 * (n-1) 和1个 n * (m-2) 的矩形巧克力块,显然这三块都是可以分割成 1 * 2 的小块的。
3.如果nm都是偶数,那么考虑对一个 n * m 的巧克力块进行 0 1 染色,使得与0相邻的方块都染色成 1,与1相邻的方块都染色成 0,那么显然,左上角和右下角被切去的方块的颜色是相同的,所以剩下的染色为0的方块和染色为1的方块的数量差为2,又因为每次切下一个1*2的方块都会使得染色为0和染色为1的方块数量同时减去1,所以最后一定会剩下两个颜色相同的方块,所以不可能完全切割。
代码:
#include
using namespace std;
int main()
{
int n,m;
while(scanf("%d%d",&n,&m)!=EOF)
{
if((n^m)&1)printf("Yes\n");
else printf("No\n");
}
}
典型的树状结构,先一遍dfs处理出所有人的效率值,然后一遍dp出所有节点到根节点区间的最大效率值,然后用tarjan算法算出m个节点的最近公共祖先,该节点的dp值就是答案,但是要注意,当lca点在m个节点内时,答案就是父节点的dp值。由于m总和<=n,倍增一般会被卡掉,需要用tarjan一次性求出全部询问的lca。
代码:
#include
#include
#include
#include
#include
#include
using namespace std;
const int maxn = 1000005;
vector <int> a[maxn];
vector <int> s[maxn];
int first[maxn];
int len[maxn];
int n,m;
int ans[maxn];
int dp[maxn];
int fa[maxn];
int pre[maxn],L[maxn],p[maxn];
void init()
{
for(int i=1;i<=n;i++)
{
pre[i]=i;
a[i].clear();
s[i].clear();
}
memset(dp,0,sizeof(dp));
memset(first,-1,sizeof(first));
}
int find(int x)
{
if(pre[x]==x)
return x;
else
{
int t=find(pre[x]);
pre[x]=t;
return t;
}
}
void join(int x,int y)
{
int a=find(x);
int b=find(y);
pre[a]=b;
}
void dfs(int x)
{
int l=a[x].size();
if(l==0)
p[x]=L[x];
else
{
int sum=0;
for(int i=0;i2;
}
}
void get_dp(int x)
{
dp[x]=max(dp[fa[x]],p[x]);
int l=a[x].size();
for(int i=0;ivoid lca(int x)
{
int l;
l=s[x].size();
for(int i=0;iint t=s[x][i];
len[t]--;
if(first[t]==-1)
first[t]=x;
if(len[t]==0)
{
int res=find(first[t]);
int flag = 0;
int ll=s[res].size();
for(int j=0;jif(s[res][j]==t)
{
flag=1;
break;
}
if(flag==1)
res=fa[res];
ans[t]=dp[res];
}
}
l=a[x].size();
for(int i=0;iint main()
{
scanf("%d%d",&n,&m);
init();
fa[1]=1;
for(int i=2;i<=n;i++)
{
scanf("%d",&fa[i]);
a[fa[i]].push_back(i);
}
for(int i=1;i<=n;i++)
scanf("%d",&L[i]);
for(int i=1;i<=m;i++)
{
scanf("%d",&len[i]);
for(int j=1;j<=len[i];j++)
{
int x;
scanf("%d",&x);
s[x].push_back(i);
}
}
dfs(1);
get_dp(1);
lca(1);
for(int i=1;i<=m;i++)
printf("%d\n",ans[i]);
return 0;
}
裸的网络流最小割定理的应用,是经典的方格取数的三维版,卡EK,dinic就能过。
代码:
#include
#include
#include
#include
#include
using namespace std;
const int maxn = 21;
vector <int> e[maxn*maxn*maxn];
const int INF=0x3f3f3f3f;
struct node
{
int w;
int to;
}f[maxn*maxn*maxn*20];
int a[maxn][maxn][maxn];
int id[maxn][maxn][maxn];
int d[maxn*maxn*maxn];
int sum,cnt,s,num;
bool bfs()
{
memset(d,-1,sizeof(d));
d[0]=0;
queue<int> q;
q.push(0);
while(!q.empty())
{
int x=q.front();
q.pop();
int l=e[x].size();
for(int i=0;iint t=e[x][i];
if(d[f[t].to]==-1&&f[t].w>0)
{
d[f[t].to]=d[x]+1;
q.push(f[t].to);
}
}
}
if(d[sum+1]>0) return true;
else return false;
}
int dfs(int x,int ff)
{
if(x==sum+1||ff==0)
return ff;
int s=0,fl;
int l=e[x].size();
for(int i=0;iint t=e[x][i];
if(d[f[t].to]==d[x]+1&&(fl=dfs(f[t].to,min(ff,f[t].w)))>0)
{
s+=fl;
ff-=fl;
f[i].w-=fl;
f[i^1].w+=fl;
}
}
return s;
}
int dinic()
{
int ans=0;
while(bfs())
{
ans +=dfs(0,INF);
}
return ans;
}
void add_edge(int from,int to,int w)
{
cnt++;
f[cnt].to=to;
f[cnt].w=w;
e[from].push_back(cnt);
cnt++;
f[cnt].to=from;
f[cnt].w=0;
e[to].push_back(cnt);
}
int main()
{
int n,m,h=0;
freopen("test.in","r",stdin);
freopen("test.output","w",stdout);
scanf("%d%d%d",&n,&m,&h);
sum=n*m*h;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
for(int k=1;k<=h;k++)
{
scanf("%d",&a[i][j][k]);
s+=a[i][j][k];
}
cnt=-1;
num=0;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
for(int k=1;k<=h;k++)
if((i+j+k)%2==1)
{
add_edge(0,++num,a[i][j][k]);
id[i][j][k]=num;
}
else
{
add_edge(++num,sum+1,a[i][j][k]);
id[i][j][k]=num;
}
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
for(int k=1;k<=h;k++)
{
if((i+j+k)%2==1)
{
if(i+1<=n) add_edge(id[i][j][k],id[i+1][j][k],INF);
if(j+1<=n) add_edge(id[i][j][k],id[i][j+1][k],INF);
if(k+1<=n) add_edge(id[i][j][k],id[i][j][k+1],INF);
if(i-1>0) add_edge(id[i][j][k],id[i-1][j][k],INF);
if(j-1>0) add_edge(id[i][j][k],id[i][j-1][k],INF);
if(k-1>0) add_edge(id[i][j][k],id[i][j][k-1],INF);
}
}
int ans=dinic();
printf("%d\n",s-ans);
return 0;
}
设 Tk T k 为以访问 k k 个不同的点的首次到达事件。则 T1=0,T2=1 T 1 = 0 , T 2 = 1
设 r(k)=E(Tk–Tk−1)k=3,4…N r ( k ) = E ( T k – T k − 1 ) k = 3 , 4 … N 其值只与 N N 有关。
在时刻 Tk−1 T k − 1 处在边界点 X(Tk−1) X ( T k − 1 ) 且其相邻节点之一已被访问,另一个尚未访问,下一步会走到两节点之一 。
如果走到已访问节点,那么将会继续随机游动直到到达下一个边界节点,从已访问的节点到下一个边界的期望为 k–3 k – 3 。
所以:
r(k)=1+12[(k−3)+r(k)] r ( k ) = 1 + 1 2 [ ( k − 3 ) + r ( k ) ]
代码:
#include
using namespace std;
int main()
{
long long int n;
while(scanf("%lld",&n)!=EOF)
printf("%lf\n",(double)(n*(n-1)/2));
return 0;
}
因为盒子里面的数字为 [1,n] [ 1 , n ] ,且每个数字只出现了一次,所以会形成一个 n n 的置换,只要判断这个 n n 的置换是否存在一个大于 n2 n 2 的循环节,就可以知道这种策略是否可以使每个人都活下来。
第二问就是对于给定的 n n ,计算 n! n ! 种 n n 的全排列里面有多少全排列满足要求:“不存在长度不超过 n2 n 2 的循环节”。
简单地容斥+枚举一下就可以得到答案,注意要与处理一下阶乘的前缀和以及它们对应的逆元。另外最后还要求一个逆元。
代码:
#include
using namespace std;
#define maxn 1000052
#define MOD 1000000007
#define mod(x) ((x)%MOD)
int a[maxn],vis[maxn],n;
long long int fi[maxn];
long long int fast_pow(long long int x,int k)
{
long long int ret=1;
x=mod(x);
while(k>0)
{
if(k&1)ret=mod(ret*x);
x=mod(x*x);
k>>=1;
}
return ret;
}
long long int inv(long long int x)
{
return fast_pow(x,MOD-2);
}
void init()
{
fi[0]=1;
long long int i=1;
for(i=1;i1]*i);
}
}
int main()
{
long long int son=0,mot=0;
init();
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
}
memset(vis,0,sizeof(vis));
int ans=0,now,cnt;
for(int i=1;i<=n;i++)
{
if(vis[i]!=0)continue;
now=a[i];cnt=1;vis[i]=1;
while(now!=i)
{
vis[now]=1;
cnt++;now=a[now];
}
ans=max(ans,cnt);
}
mot=mod( fi[n]*inv(fi[n/2-1]) );
long long int i;
for(i=n/2+1;i<=n;i++)
{
son=mod(son+ mod( mot*inv(i) ) );
}
son=mod(mod(mot-son)+MOD);
if(ans<=n/2)printf("Yes ");
else printf("No ");
printf("%lld\n",mod(son*inv(mot)));
return 0;
}