HGOI20190710 题解
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 题解的更多相关文章
- 2016 华南师大ACM校赛 SCNUCPC 非官方题解
我要举报本次校赛出题人的消极出题!!! 官方题解请戳:http://3.scnuacm2015.sinaapp.com/?p=89(其实就是一堆代码没有题解) A. 树链剖分数据结构板题 题目大意:我 ...
- noip2016十连测题解
以下代码为了阅读方便,省去以下头文件: #include <iostream> #include <stdio.h> #include <math.h> #incl ...
- BZOJ-2561-最小生成树 题解(最小割)
2561: 最小生成树(题解) Time Limit: 10 Sec Memory Limit: 128 MBSubmit: 1628 Solved: 786 传送门:http://www.lyd ...
- Codeforces Round #353 (Div. 2) ABCDE 题解 python
Problems # Name A Infinite Sequence standard input/output 1 s, 256 MB x3509 B Restoring P ...
- 哈尔滨理工大学ACM全国邀请赛(网络同步赛)题解
题目链接 提交连接:http://acm-software.hrbust.edu.cn/problemset.php?page=5 1470-1482 只做出来四道比较水的题目,还需要加强中等题的训练 ...
- 2016ACM青岛区域赛题解
A.Relic Discovery_hdu5982 Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/65536 K (Jav ...
- poj1399 hoj1037 Direct Visibility 题解 (宽搜)
http://poj.org/problem?id=1399 http://acm.hit.edu.cn/hoj/problem/view?id=1037 题意: 在一个最多200*200的minec ...
- 网络流n题 题解
学会了网络流,就经常闲的没事儿刷网络流--于是乎来一发题解. 1. COGS2093 花园的守护之神 题意:给定一个带权无向图,问至少删除多少条边才能使得s-t最短路的长度变长. 用Dijkstra或 ...
- CF100965C题解..
求方程 \[ \begin{array}\\ \sum_{i=1}^n x_i & \equiv & a_1 \pmod{p} \\ \sum_{i=1}^n x_i^2 & ...
随机推荐
- (二)Java秒杀项目之实现登录功能
一.数据库设计 CREATE TABLE `miaosha_user` ( `id` ) NOT NULL COMMENT '用户ID,手机号码', `nickname` ) NOT NULL, `p ...
- SQL之事务
●事务的ACID(acid)属性 ➢1.原子性(Atomicity ) 原子性是指事务是-一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生. ➢2. 一致性(Consistency) 事务 ...
- 测试必知150个常用Linux命令,已为各位筛选整理
●线上查询及帮助命令(1 个) help 如:mkdir --help ●文件和目录操作命令(12 个) ls tree pwd mkdir rmdir cd touch cp mv r ...
- python 写接口供外部调用
.py: import requests import urllib2 import commands import subprocess def check(): status, msg = com ...
- Linux-1.4文件操作命令(grep,cat,tail,head,less,find,chmod,tail,less)
Linux基础命令(grep,cat,tail,head,less,find,chmod,tail,less) grep(常用) grep 指定“文件”搜索文件内容 grep hello 1.txt ...
- Stream 分布式数据流的轻量级异步快照
1. 概述 分布式有状态流处理支持在云中部署和执行大规模连续计算,主要针对低延迟和高吞吐量.这种模式的一个最根本的挑战就是在可能的失败情况下提供处理保证.现有方法依赖于可用于故障恢复的周期性全局状态快 ...
- CSS行高——line-height 垂直居中等问题
CSS行高——line-height 初入前端的时候觉得CSS知道display.position.float就可以在布局上游刃有余了,随着以后工作问题层出不穷,才逐渐了解到CSS并不是几个sty ...
- openCV3 Python编译指南
这里主要对openCV官网的<Installation in Linux>文档进行了翻译和解释 原文见:https://docs.opencv.org/3.4.1/doc/tutorial ...
- 【转载】java工程师学习之路---给自己的目标
想学习或者提升java的可以看看,单从java角度来看总结的虽然还是很全面的,主要是为了自己看 http://blog.csdn.net/peace1213/article/details/50849 ...
- vi编辑器中删除文件中所有字符
在命令模式下,将光标移动到文档最上方(使用gg命令),然后输入dG,删除工作区内所有缓存数据. 如果想要删除某行文档以下的内容,将光标移动到文档相应行,然后输入dG即可.