HGOI 20191106 题解

Problem A  旅行者

有$n$种转移装置,每种转移装置本质相同,每种装置可以前进$a_i$单位,但只有$b_i$个。

从初始坐标为$0$出发,途中不能经过$c_1,c2,...,c_m$中的任意一个点。

走到$\sum\limits_{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 < \sum\limits_{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(n\prod\limits_{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\n",f[a[1].b][a[2].b][a[3].b][a[4].b][a[5].b][a[6].b]%mo);  
    return 0;
}
traveller.cpp

Problem B 序列

定义两个数$(x,y)$是好的,当且仅当$x \leq y$且$x \oplus y$二进制表示下含有奇数个$1$。

现在给出$n$个区间$[l_i,r_i]$,对于每一个$i\in[1,n]$,输出前$i$个区间并中好的数对的个数。

即输出满足下列条件的$(x,y)$的对数。

  • $x,y \in\cup_{j=1}^i [l_j,r_j]$
  • $x\leq y$
  • $x \oplus y$二进制表示下含有奇数个$1$。

对于$100\%$的数据满足$1 \leq n \leq 10^5 , 1\leq l_i\leq r_i\leq 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('\n');
    }
    return 0;
}
sequence.cpp

Problem C 钢琴家

给出一个基本字符串$S$,给出$n$个目标字符串集合$t_i$。

要求修改一些$S_i$使得可以将$S$划分成一些子串,使得这些子串都在目标字符串集合中出现。

让替换次数尽可能小,且保证存在至少一个最优解。

对于$100\%$的数据,满足 $1 \leq |t_i| \leq |S|\leq 10^3 , 1\leq n\leq 10^2​$

 ​ Solution :

  本题可以直接用$DP$求出最优策略,设$f[i]$表示匹配到长度$i$的最小代价。

​   枚举一个目标字符串$1 \leq j \leq n$,可以考虑$[i-|t_j|+1 , i]$用串$t_j$来修改。

​   然后计算这样做的代价,就是$\sum\limits_{k = 1}^{|t_j|} [\ S[i-|t_j|+k]\neq 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('\n');    
    }
    return 0;
}
pianist.cpp

 

posted @ 2019-11-06 12:44  ljc20020730  阅读(196)  评论(0编辑  收藏  举报