HGOI 20191106 题解
Posted ljc20020730
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了HGOI 20191106 题解相关的知识,希望对你有一定的参考价值。
有$n$种转移装置,每种转移装置本质相同,每种装置可以前进$a_i$单位,但只有$b_i$个。
从初始坐标为$0$出发,途中不能经过$c_1,c2,...,c_m$中的任意一个点。
走到$sumlimits_{i = 1}^n a_ib_i$位置的方案数$mod 10^9 + 7$的值。
对于$100\%$的数据满足$1 leq n leq 6 , 1 leq m leq 10^5 ,0<c_i < sumlimits_{i = 1}^n a_ib_i$
Solution :
由于每个装置本质相同,那么我们只需要记录当前使用的转移装置数作为状态即可。
这样定义状态的总状态数时$prod_{i = 1}^n b_i leq 13^6 = 4826809$
注意,由于有$m$点不能走,还需要开一个$hash$存当前值能不能走,特殊判掉即可。
转移的时候枚举当前通过那个转移装置走到当前位置,转移时间复杂度为$O(n)$
请注意,本题的模数为$10^8 + 7$,您是否数错了零?
所以,本题的总时间复杂度是$O(nprodlimits_{i=1}^{n} b_i)$
# pragma GCC optimize(3) # include<bits/stdc++.h> # define int long long # define hash Hash # define Rint register int using namespace std; const int mo=100000007; int f[13][13][13][13][13][13]; struct rec{int a,b;}a[10]; int n,m; vector<int>hash[10007]; inline int read() { int X=0,w=0; char c=0; while(c<‘0‘||c>‘9‘) {w|=c==‘-‘;c=getchar();} while(c>=‘0‘&&c<=‘9‘) X=(X<<3)+(X<<1)+(c^48),c=getchar(); return w?-X:X; } void insert(int key) { int k = key % 10007,sz = hash[k].size(); for (int i=0;i<sz;i++) if (hash[k][i] == key) return; hash[k].push_back(key); } bool find(int key) { int k=key % 10007,sz = hash[k].size(); for (int i=0;i<sz;i++) if (hash[k][i] == key) return true; return false; } signed main() { n=read(); for (Rint i=1;i<=n;i++) { a[i].a=read(); a[i].b=read(); } m=read(); for (Rint i=1;i<=m;i++) { int t=read(); insert(t); } f[0][0][0][0][0][0]=1; for (Rint a1=0;a1<=a[1].b;a1++) for (Rint a2=0;a2<=a[2].b;a2++) for (Rint a3=0;a3<=a[3].b;a3++) for (Rint a4=0;a4<=a[4].b;a4++) for (Rint a5=0;a5<=a[5].b;a5++) for (Rint a6=0;a6<=a[6].b;a6++) { int tmp = a1*a[1].a+a2*a[2].a+a3*a[3].a+a4*a[4].a+a5*a[5].a+a6*a[6].a; if (find(tmp)) { f[a1][a2][a3][a4][a5][a6]=0; continue; } if (a1+1<=a[1].b) (f[a1+1][a2][a3][a4][a5][a6]+=f[a1][a2][a3][a4][a5][a6])%=mo; if (a2+1<=a[2].b) (f[a1][a2+1][a3][a4][a5][a6]+=f[a1][a2][a3][a4][a5][a6])%=mo; if (a3+1<=a[3].b) (f[a1][a2][a3+1][a4][a5][a6]+=f[a1][a2][a3][a4][a5][a6])%=mo; if (a4+1<=a[4].b) (f[a1][a2][a3][a4+1][a5][a6]+=f[a1][a2][a3][a4][a5][a6])%=mo; if (a5+1<=a[5].b) (f[a1][a2][a3][a4][a5+1][a6]+=f[a1][a2][a3][a4][a5][a6])%=mo; if (a6+1<=a[6].b) (f[a1][a2][a3][a4][a5][a6+1]+=f[a1][a2][a3][a4][a5][a6])%=mo; } printf("%lld ",f[a[1].b][a[2].b][a[3].b][a[4].b][a[5].b][a[6].b]%mo); return 0; }
定义两个数$(x,y)$是好的,当且仅当$x leq y$且$x oplus y$二进制表示下含有奇数个$1$。
现在给出$n$个区间$[l_i,r_i]$,对于每一个$iin[1,n]$,输出前$i$个区间并中好的数对的个数。
即输出满足下列条件的$(x,y)$的对数。
- $x,y incup_{j=1}^i [l_j,r_j]$
- $xleq y$
- $x oplus y$二进制表示下含有奇数个$1$。
对于$100\%$的数据满足$1 leq n leq 10^5 , 1leq l_ileq r_ileq 2^{32}-1$
Solution :
? 首先,$x oplus y$的二进制表示下有奇数个$1$有充要条件:一个数有偶数个$1$,一个数有奇数个$1$ 。
? 设$x,y$按二进制位写开来,同$1$的数的对数为$w$,那么剩余的奇数个$1$和偶数个$1$都会对答案产生$1$的贡献。
? 如果我们要统计数集$S$中好的数对的个数,我们就只要数一数这个数集中有多少个数含有偶数个$1$,有多少数有奇数个一,即可。最后的答案就是他们两个之积。
? 如何求一个区间$[l,r]$内有多少个数含有奇数个$1$或者偶数个$1$呢。
? 我们只要求前缀和即可,问题转化为求$[1,x]$的答案。
? 观察到一对数$0,1 ; 2, 3 ; 4,5 ... $从$0$开始每两个数一组一定是一个数字含有奇数个$1$,另外一个数字含有偶数个$1$.
? 所以,若$x$是奇数,$[1,x]$的答案直接是$x/2$了; 否则还需要特殊判断$x$这个数字到底是含有奇数个$1$,另外一个数字含有偶数个$1$.
? 接下来的问题就转化为线段的并了,我们可以很方便的用主席树来解决。
? 对值域建动态开点的线段树,当前区间如果被覆盖了直接打上一个$vis$标记,下一次不覆盖当前区间即可。
? 设数的值域是$S$,那么这样做的时间复杂度是$O(n log_2S)$
# pragma GCC optimize(3) # include <bits/stdc++.h> # define int long long # define lowbit(x) (x&(-x)) const int N=1e5+10; using namespace std; int n,tot; inline int read() { int X=0,w=0; char c=0; while(c<‘0‘||c>‘9‘) {w|=c==‘-‘;c=getchar();} while(c>=‘0‘&&c<=‘9‘) X=(X<<3)+(X<<1)+(c^48),c=getchar(); return w?-X:X; } void write(int x) { if (x<0) x=-x,putchar(‘-‘); if (x>9) write(x/10); putchar(‘0‘+x%10); } int query(int x) { if (x&1) return x/2+1; int ret=0,res=x/2; while (x) { x-=lowbit(x); ret^=1;} return res+ret; } bool vis[N*32]; struct Seg { int ls,rs,cnt1,cnt2; }tr[N*32]; # define ls tr[x].ls # define rs tr[x].rs void update(int &x,int l,int r,int opl,int opr) { if (!x) x=++tot; if (vis[x]) return; if (opl<=l && r<=opr) { vis[x]=1; tr[x].cnt1=query(r)-query(l-1); tr[x].cnt2=r-l+1-tr[x].cnt1; return; } int mid=(l+r)>>1; if (opl<=mid) update(ls,l,mid,opl,opr); if (opr>mid) update(rs,mid+1,r,opl,opr); tr[x].cnt1=tr[ls].cnt1+tr[rs].cnt1; tr[x].cnt2=tr[ls].cnt2+tr[rs].cnt2; } signed main() { n=read(); int root=0; for (int i=1;i<=n;i++) { int l=read(),r=read(); update(root,1,(1ll<<63)-1,l,r); write(tr[1].cnt1*tr[1].cnt2); putchar(‘ ‘); } return 0; }
给出一个基本字符串$S$,给出$n$个目标字符串集合$t_i$。
要求修改一些$S_i$使得可以将$S$划分成一些子串,使得这些子串都在目标字符串集合中出现。
让替换次数尽可能小,且保证存在至少一个最优解。
对于$100\%$的数据,满足 $1 leq |t_i| leq |S|leq 10^3 , 1leq nleq 10^2?$
? Solution :
本题可以直接用$DP$求出最优策略,设$f[i]$表示匹配到长度$i$的最小代价。
? 枚举一个目标字符串$1 leq j leq n$,可以考虑$[i-|t_j|+1 , i]$用串$t_j$来修改。
? 然后计算这样做的代价,就是$sumlimits_{k = 1}^{|t_j|} [ S[i-|t_j|+k] eq t_j[k] ]$
? 记录每一次最优的转移是从何而来,这样可以输出方案。
? 最后直接按照最优方案输出即可。
? 时间复杂度为$O(n|S|^2)$
# pragma GCC optimize(3) # include <bits/stdc++.h> using namespace std; const int N=1e3+10; char t[N][N],s[N]; int n,f[N],len[N]; int pre[N]; int ans[N]; int main() { scanf("%s",s+1);int l=strlen(s+1); scanf("%d",&n); for (int i=1;i<=n;i++) { scanf("%s",t[i]+1); len[i]=strlen(t[i]+1); } memset(f,0x3f,sizeof(f)); f[0]=0; for (int i=1;i<=l;i++) for (int j=1;j<=n;j++) if (i>=len[j]){ int res = 0; for (int k=1;k<=len[j];k++) { if (t[j][k]!=s[i-len[j]+k]) res++; } if (f[i-len[j]]+res<f[i]) { f[i] = f[i-len[j]]+res; pre[i] = j; } } int now = l; while (true) { if (now == 0) break; ans[++ans[0]] = pre[now]; now=now-len[pre[now]]; } for (int i=ans[0];i>=1;i--) { for (int j=1;j<=len[ans[i]];j++) putchar(t[ans[i]][j]); putchar(‘ ‘); } return 0; }
以上是关于HGOI 20191106 题解的主要内容,如果未能解决你的问题,请参考以下文章