Problem A 游戏

有$n (1 \leq n \leq 5)$个硬币初始以"0"(正面),"1"(反面) $m (1 \leq m \leq m)$种操作,每一种操作可以将一些硬币进行翻转。

但是有$ k (0 \leq k < \frac{(m-1)\times m}{2}$) 组限制,每组限制描述$A$操作和$B$操作互不能连续进行。

已知翻转游戏进行$T$轮,求$T$轮之后,硬币全部翻转成反面朝上的方案数。

对于100%的数据 $1 \leq T \leq 10^5$

对于真正的100%的数据 $1 \leq T \leq 10^9 $

Sol : 对于100%的数据发现T的值比较小,暴力状压DP即可。

  设 $f_{i,j,k}$ 表示当前是第 $i$ 轮末,当前硬币的状态是 $j$ ,上一次转移而来的翻转操作的编号为 $k$。

  转移方程为$f_{i,j,k} = \sum\limits_{j'=0,k'=1 }^{j' \leq 2^n-1 , k' \leq m} f_{i-1,j',k'}$,

  其中限制是$j'$可以通过操作$k$变为$j$,并且操作$k$和操作$k'$可以连续进行。

  所以,这种算法的时间复杂度是$O(T\times 2^n m n )$,可以AC。

# pragma GCC optimize()
# include <bits/stdc++.h>
using namespace std;
const int mo=1e9+;
int a[],n,m,k,T,tmp[];
vector<int>v[];
int f[][][];
int main()
{
scanf("%d%d%d",&n,&m,&k);
int start=;
for (int i=;i<=n;i++) {
int t; scanf("%d",&t);
start=(start<<)+t;
}
for (int i=;i<=n;i++)
for (int j=;j<=n;j++)
v[i].push_back(j);
for (int i=;i<=m;i++) {
int t; scanf("%d",&t);
memset(tmp,,sizeof(tmp));
for (int j=;j<=t;j++) { int o; scanf("%d",&o); tmp[o]=;}
int rr=;
for (int j=;j<=n;j++) rr=(rr<<)+tmp[j];
a[i]=rr;
} for (int i=;i<=k;i++) {
int x,y; scanf("%d%d",&x,&y);
for (int j=;j<v[x].size();j++)
if (v[x][j]==y) v[x].erase(v[x].begin()+j);
for (int j=;j<v[y].size();j++)
if (v[y][j]==x) v[y].erase(v[y].begin()+j);
} memset(f,,sizeof(f));
for (int i=;i<=m;i++) {
f[][start xor a[i]][i]=;
} scanf("%d",&T);
for (int i=;i<=T;i++)
for (int j=;j<=(<<n)-;j++)
for (int k=;k<=m;k++)
for (int t=;t<v[k].size();t++) {
int w=v[k][t];
f[i][j][k]=(f[i-][j^a[k]][w]+f[i][j][k])%mo;
}
int ans=;
for (int i=;i<=m;i++) ans=(ans+f[T][(<<n)-][i])%mo;
printf("%d\n",ans); return ;
}

Game_100.cpp

  而对于真正100%的数据则要使用矩阵加速DP,考虑到状态$(j',k')$可以变成$(j,k)$的条件是$j'$可以通过操作$k$变为$j$,并且操作$k$和操作$k'$可以连续进行。

  所以我们可以预处理出一个矩阵表示从某一个状态的二元组到另外一个状态的二元组的转移是否可行,如果可行置为1,不可行置为0.

  如何在普通的矩阵中表示一个转移的二元组呢?可以Hash一下,把二元组$ (j,k) $表示成$ j\times m + k$即可完成转移。

  实现方面,先预处理出T=1时的情况(不存在转移的合法性)作为初始矩阵,然后若转移$(j',k')$到$(j,k)$合法,那么就将$A[j'm+k'][jm+k]$置为1,否则置为0.

  然后对于矩阵A计算$A^{T-1}$表示,这个相同的转移方式一共进行T-1次,获得的矩阵再乘以初始矩阵就是最终的答案矩阵$Ans$了,

  最终的答案只需对$Ans$矩阵每个元素求和即可。

  复杂度是$O(log_2 T (2^n m + k)^3 )$足以通过$T\leq 10^9$的数据。

#include <fstream>
#include <algorithm>
using namespace std; ifstream in("game.in");
ofstream out("game.out"); const int N = ;
const long long P = ;
const int M = ; int operatorlist[M];
int n, m, t, s;
int start_bit = ;
int ban[M][M];
long long tmp[N][N];
long long result[N][N]; void matrix_multiplication(int n, long long A[N][N], long long B[N][N])
{
int C[N][N];
for (int i=; i<n; i++)
for (int j=; j<n; j++)
{
C[i][j] = ;
}
for (int i=; i<n; i++)
for (int j=; j<n; j++)
for (int k=; k<n; k++)
{
C[i][j] =(C[i][j]+(A[i][k]*B[k][j] % P))%P;
}
for (int i=; i<n; i++)
for (int j=; j<n; j++)
{
A[i][j] = C[i][j];
}
} void matrix_addition(int n, long long A[N][N], long long B[N][N])
{
for (int i=; i<n; i++)
for (int j=; j<n; j++)
{
A[i][j]=(A[i][j]+B[i][j]) % P;
}
} void input()
{
in>>n>>m>>t;
for (int i=; i<n; i++)
{
int x;
in>>x;
start_bit = (start_bit<<)+x;
}
for (int i=; i<m; i++)
{
int k, op = ;
in>>k;
for (int j=; j<k; j++)
{
int x;
in>>x;
op += <<(n-x);
}
operatorlist[i+] = op;
}
for (int i=; i<t; i++)
{
int x, y;
in>>x>>y;
ban[x][y] = ;
ban[y][x] = ;
}
in>>s;
}
void work()
{
for (int i=; i<(<<n); i++)
for (int j=; j<m; j++)
for (int k=; k<m; k++)
if (ban[j+][k+]!=)
{
int next_i = i^operatorlist[k+];
tmp[i*m+j][next_i*m+k] = ;
}
int nn = (<<n)*m;
bool first = true;
s-=;
while (s>)
{
if (s&)
{
if (first){
matrix_addition(nn, result, tmp);
first = false;
}else
{
matrix_multiplication(nn, result, tmp);
} }
matrix_multiplication(nn, tmp, tmp);
s>>=;
}
} void output()
{
long long ans =;
for (int i=; i<m; i++)
for (int j=; j<m; j++)
{
ans = (ans + result[(start_bit^operatorlist[j+])*m+j][((<<n)-)*m+i]) % P;
}
out<<ans<<endl;
in.close();
out.close();
} int main()
{
freopen("game.in", "r", stdin);
freopen("game.out", "w", stdout);
input();
work();
output();
}

Game_100.cpp

 Problem B  探险

  给出一棵含有$n$个节点的树,和$m$条路径$s_i,t_i,d_i$完成下列两个任务。

  Rask1 : 对于每一条路径输出不在路径上的节点最小点的编号,(强制在线),

  Rask2:询问是否存在一个点$u$使得$u$到达路径$i$的距离(距离定义为点到路径的最短距离)都小于等于$d_i$ (要求输出"YES"和"NO")

  对于100%的数据$1 \leq n,m \leq 10^5$

Sol : 对于第一问,对每个节点都在其父亲的基础上建立可持久化线段树维护从根节点到每个节点路径上的编号在值域区间出现的总次数。

   对于一条路径$s_i , t_i ,d_i$求出$s_i,t_i$的LCA为$l_i$而$l_i$的直接父亲为$L_i$ ,

  由于每棵线段树的结构相同,所以在遍历线段树查询的时候,直接对$s_i$节点维护线段树+$t_i$节点维护的线段树-$L_i$-$l_i$节点维护的线段树的值域进行查找即可。

  这本质上用权值线段树对值域进行的二分查找。复杂度$O(n log_2 n)$

  对于第二问,贪心求解,对于每一条路径$s_i,t_i,d_i$求出$s_i,t_i$的LCA$l_i$,然后在$l_i$上跳$d_i$条边所得得节点设为$r_i$,如果不存在(跳到根节点以上)则不考虑。

  记深度最大的$r_i$节点为$R$,那么$R$是最优选取的点$u$,(其他可能的$u$可行的$R$一定可行,$R$若可行,其他可能节点$u$不一定行)。

  我们对上述贪心结论可以做出如下证明: (若 $R$节点对$s_i , t_i , d_i$不可行)

    1. 如果 $s_i , t_i $ 在$R$所在的子树中,那么其上跳$d_i$ 后若不可到达$R$ (由于其不可行) ,而与R的深度最大性不符合,不可能。

    2.如果 $s_i ,t_i $有$1$个节点在$R$所在子树中,那么路径必然经过节点$R$,所以其距离应该是$0$,而$d_i \geq 0$是必然的,不符合,不可能。

    3.根据1,2两条结论我们可以发现,$s_i , t_i $必然不在$R$的子树中, 为了联系这个探险家,这个节点必然需要到$R$的子树外,所以在$R$的子树内的某一个探险家就无法被联系,不可能。

  综上所述,$R$必然是最优节点,所以我们只需要计算$R$与每条路径的距离即可。

  如果$R$被某一条路径经过,那么$R$到这条路径的距离是$0$ 否则就是$R$到$lca(s_i,t_i)$的距离。

 $Update : $怎么判断一个点$x$在树的路径$(u,v)$上呢?

 只要其$(dep_x \geq dep_{lca}) \ And \ (LCA(u,x) = R) \ And \ (u$向上跳$dep_u - dep_x $步是$x)$ 或 $(dep_x \geq dep_{lca}) \ And \ (LCA(v,x) = R) \ And \ (v$向上跳$dep_v - dep_x $步是$x)$ 即可

  上述的两个问题总时间复杂度为$O(m log_2 n )$即可完成。

  Ps : 人傻自带大常数。

#pragma GCC optimize(3)
# include<bits/stdc++.h>
using namespace std;
const int N=5e5+;
const int B=;
struct Path { int s,t,lca,d; }path[N];
struct Segment_Tree{ int ls,rs,cnt;}t[N*];
struct rec{ int pre,to;}a[N<<];
int root[N*],g[N][],head[N],dep[N];
int n,m,tot,size;
int R,valR;
inline int read()
{
int X=,w=; char c=;
while(c<''||c>'') {w|=c=='-';c=getchar();}
while(c>=''&&c<='') X=(X<<)+(X<<)+(c^),c=getchar();
return w?-X:X;
}
inline void write(int x)
{
if (x>) write(x/);
putchar(x%+'');
}
void clear()
{
tot=; size=; R=; valR=; root[]=;
memset(head,,sizeof(head));
memset(t,,sizeof(t));
memset(a,,sizeof(a));
}
void adde(int u,int v)
{
a[++tot].pre=head[u];
a[tot].to=v;
head[u]=tot;
}
#define lson t[rt].ls,l,Mid
#define rson t[rt].rs,Mid+1,r
#define Mid ((l+r)>>1)
void insert(int last,int &rt,int l,int r,int val)
{
rt=++size; t[rt]=t[last];
if (l==r) {t[rt].cnt++;return;}
if (val<=Mid) insert(t[last].ls,lson,val);
else insert(t[last].rs,rson,val);
t[rt].cnt=t[t[rt].ls].cnt+t[t[rt].rs].cnt;
}
int query(int ru,int rv,int rlca,int rfa,int l,int r)
{
if (l==r) return l;
int ret=t[t[ru].ls].cnt+t[t[rv].ls].cnt-t[t[rlca].ls].cnt-t[t[rfa].ls].cnt;
if (Mid-l+>ret) return query(t[ru].ls,t[rv].ls,t[rlca].ls,t[rfa].ls,l,Mid);
else return query(t[ru].rs,t[rv].rs,t[rlca].rs,t[rfa].rs,Mid+,r);
}
void dfs(int u,int fath)
{
g[u][]=fath; dep[u]=dep[fath]+;
insert(root[fath],root[u],,n,u);
for (int i=head[u];i;i=a[i].pre) {
int v=a[i].to; if (v==fath) continue;
dfs(v,u);
}
}
int LCA(int u,int v)
{
if (dep[u]<dep[v]) swap(u,v);
for (int i=;i>=;i--)
if (dep[g[u][i]]>=dep[v]) u=g[u][i];
if (u==v) return u;
for (int i=;i>=;i--)
if (g[u][i]!=g[v][i]) u=g[u][i],v=g[v][i];
return g[u][];
}
void init() {
for (int i=;i<=;i++)
for (int j=;j<=n;j++)
g[j][i]=g[g[j][i-]][i-];
}
void jump(int u,int d)
{
if (dep[u]-dep[]<d) return;
for (int i=;i>=;i--)
if (d>=(<<i)) d-=(<<i),u=g[u][i];
if (dep[u]>valR) valR=dep[u],R=u;
}
int jumpto(int u,int d)
{
if (d<) return -;
for (int i=;i>=;i--)
if (d>=(<<i)) d-=(<<i),u=g[u][i];
return u;
}
int dis(int u,int v)
{
int lca=LCA(u,v);
return dep[u]+dep[v]-*dep[lca];
}
int main()
{
int T=read();
while (T--) {
clear();n=read();m=read();
for (int i=;i<n;i++) {
int u=read(),v=read();
adde(u,v); adde(v,u);
}
dfs(,); init();
for (int i=;i<=m;i++) {
int s=read(),t=read(),d=read();
int lca=LCA(s,t); jump(lca,d);
int ans=query(root[s],root[t],root[lca],root[g[lca][]],,n);
write(ans);putchar('\n');
path[i].s=s; path[i].t=t; path[i].lca=lca; path[i].d=d;
}
bool check=true;
for (int i=;i<=m;i++) {
int u=path[i].s,v=path[i].t,lca=path[i].lca,d=path[i].d;
if (dep[R]>=dep[lca]&&LCA(u,R)==R&&jumpto(u,dep[u]-dep[R])==R) continue;
if (dep[R]>=dep[lca]&&LCA(v,R)==R&&jumpto(v,dep[v]-dep[R])==R) continue;
if (dis(R,lca)>d) { check=false; break; }
}
if (check) putchar('Y'),putchar('E'),putchar('S'),putchar('\n');
else putchar('N'),putchar('O'),putchar('\n');
}
return ;
}

explore.cpp

Problem C 或

T组询问,给出两个01串$a_i$和$b_i$,要求出一个$c_i$,

若在$a_i$和$b_i$中存在$a_x = 1$,$b_y = 1$,并且$x or y = i$,那么$c_i = 1$否则$c_i = 0$

对于100%的数据$1 \leq |a| = |b| \leq 2\times 10^5$

Sol:  第一次接触二进制高维前缀和。

  我们对$i \in [1,n]$分别考虑,如果$i$的二进制位表达$I$可以拆分成两个子集$A$和$B$并且使得$A \cup  B = I$

  并且以二进制表达的A表示的十进制数作为下标$i$在$a_i$中不为0,并且以二进制表达B表示的十进制数作为下标的$j$在$b_j$中也不为0。

  那么$c_i = 1$否则$c_i = 0$ ,这是对原来题目的一步转化。

  为了判断当前位$i$是否合法,我们需要知道$s_i=\sum_{J\sqsubseteq I} a_j$ 其中$I$表示$i$的二进制表示,$J$表示$j$的二进制表示。

  为了数学严谨上述公式可以被严谨的表示为$ s_i = \sum _{j \ \& \ i \ = \ i} a_j $

  我们如何对于上述定义下 某一数组$a_i$的对应的$s_i$数组,称为a的子集和。

  我们可以把a数组下标$i$当做二进制位拆分,一个“01”位表示一维,那么$s_i$就是$a_i$的$log_2 n $维前缀和。

  于是可以这么处理,先从低到高枚举维度$i$,然后枚举每一个下标$j$,如果该下标的第$j$位是$1$那么就是$i-1$维度$j$第$i$位是0或1两种情况的和。

  如果该第$j$位是$1$那么就是$i-1$维度$j$第$i$位是0一种情况的和。

  上面是基于一个很简单的DP,在实现过程中我们可能会记$f_{i,j}$表示第$i$维度第$j$位置的前缀和,但是我们很快就发现$i$的记录可以被滚动。

  于是就可以简单的用如下代码计算(每一次j循环开始的时候都是i-1维度时的前缀和,0维前缀和是原数列)

for i =  -> log_2 n+ i++
for j = -> n j++
if (j 第 i 位 是 ) a[j] = a[j] + a[去掉第 i 位的 1后的十进制下标];
else a[j] = a[j]

  我们回到原来的问题,对于$a_i$数组和$b_i$数组分别求出其子集和$s_1,s_2$,令$c_i = a_i \times b_i $那么$c$就是要求$c$方案总数的高维前缀和;

  然后对$c$做高维差分(就是高维前缀和逆运算)之后就是原来$c$的值,其含义是$c_i = 1$的取值$(x,y)$的组数。

  如果$c_i = 0 $输出0否则输出1即可。

  复杂度$O(T n log_2 n)$

# include<bits/stdc++.h>
# define int long long
using namespace std;
const int N=3e5+;
char s[N];
int a[N],b[N],c[N];
signed main()
{
int T; scanf("%d",&T);
while (T--) {
scanf("%s",s+); int n=strlen(s+);
for (int i=;i<=n;i++) a[i]=s[i]-'';
scanf("%s",s+);
for (int i=;i<=n;i++) b[i]=s[i]-''; int t=log(n)/log()+; for (int i=;i<=t;i++)
for (int j=;j<=n;j++)
if (j&(<<i)) a[j]+=a[j^(<<i)]; for (int i=;i<=t;i++)
for (int j=;j<=n;j++)
if (j&(<<i)) b[j]+=b[j^(<<i)]; for (int i=;i<=n;i++) c[i]=a[i]*b[i]; for (int i=t;i>=;i--)
for (int j=;j<=n;j++)
if (j&(<<i)) c[j]-=c[j^(<<i)]; for (int i=;i<=n;i++) printf("%d",c[i]>);
puts("");
} return ;
}

or.cpp

HGOI20190710 题解的更多相关文章

  1. 2016 华南师大ACM校赛 SCNUCPC 非官方题解

    我要举报本次校赛出题人的消极出题!!! 官方题解请戳:http://3.scnuacm2015.sinaapp.com/?p=89(其实就是一堆代码没有题解) A. 树链剖分数据结构板题 题目大意:我 ...

  2. noip2016十连测题解

    以下代码为了阅读方便,省去以下头文件: #include <iostream> #include <stdio.h> #include <math.h> #incl ...

  3. BZOJ-2561-最小生成树 题解(最小割)

    2561: 最小生成树(题解) Time Limit: 10 Sec  Memory Limit: 128 MBSubmit: 1628  Solved: 786 传送门:http://www.lyd ...

  4. Codeforces Round #353 (Div. 2) ABCDE 题解 python

    Problems     # Name     A Infinite Sequence standard input/output 1 s, 256 MB    x3509 B Restoring P ...

  5. 哈尔滨理工大学ACM全国邀请赛(网络同步赛)题解

    题目链接 提交连接:http://acm-software.hrbust.edu.cn/problemset.php?page=5 1470-1482 只做出来四道比较水的题目,还需要加强中等题的训练 ...

  6. 2016ACM青岛区域赛题解

    A.Relic Discovery_hdu5982 Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/65536 K (Jav ...

  7. poj1399 hoj1037 Direct Visibility 题解 (宽搜)

    http://poj.org/problem?id=1399 http://acm.hit.edu.cn/hoj/problem/view?id=1037 题意: 在一个最多200*200的minec ...

  8. 网络流n题 题解

    学会了网络流,就经常闲的没事儿刷网络流--于是乎来一发题解. 1. COGS2093 花园的守护之神 题意:给定一个带权无向图,问至少删除多少条边才能使得s-t最短路的长度变长. 用Dijkstra或 ...

  9. CF100965C题解..

    求方程 \[ \begin{array}\\ \sum_{i=1}^n x_i & \equiv & a_1 \pmod{p} \\ \sum_{i=1}^n x_i^2 & ...

随机推荐

  1. 好的python链接

    海艳师姐博客园:  https://www.cnblogs.com/haiyan123/p/8387770.html

  2. Luogu P1600[NOIP2016]day1 T2天天爱跑步

    号称是noip2016最恶心的题 基本上用了一天来搞明白+给sy讲明白(可能还没讲明白 具体思路是真的不想写了(快吐了 如果要看,参见洛谷P1600 天天爱跑步--题解 虽然这样不好但我真的不想写了 ...

  3. [Codeforces 266E]More Queries to Array...(线段树+二项式定理)

    [Codeforces 266E]More Queries to Array...(线段树+二项式定理) 题面 维护一个长度为\(n\)的序列\(a\),\(m\)个操作 区间赋值为\(x\) 查询\ ...

  4. Java中创建的对象多了,必然影响内存和性能

    1, Java中创建的对象多了,必然影响内存和性能,所以对象的创建越少越好,最后还要记得销毁.

  5. html重置模板

    新浪的初始化: html,body,ul,li,ol,dl,dd,dt,p,h1,h2,h3,h4,h5,h6,form,fieldset,legend,img { margin: 0; paddin ...

  6. uboot 主Makefile 分析。

    本文以uboot_1.1.6 对应的CPU是S3C2440 为例 uboot_1.1.6 根目录下的主Makefile开头: VERSION = PATCHLEVEL = SUBLEVEL = EXT ...

  7. POJ题解Sorting It All Out-传递丢包+倍增

    题目链接: http://poj.org/problem?id=1094 题目大意(直接从谷歌翻译上复制下来的): 描述 不同值的递增排序顺序是其中使用某种形式的小于运算符来将元素从最小到最大排序的顺 ...

  8. redis blog

    IBM 看到的blog如何 存储在redis种 var ArticleHelper = function () { this.ArticleIDSet = "AIDSet"; // ...

  9. JMeter元件之Test Fragment

    简介 JMeter中的Test Fragment,是控制器上一个种特殊的线程组,它在测试树上与线程组处于同一层级.但使用时需要和include Controller或者Module Controlle ...

  10. 一个微信小程序跳转到另一个微信小程序

    简单来说分两步走: 1.配置项目根目录的 app.json 文件中的 navigateToMiniProgramAppIdList { "pages": [ "pages ...