NOIP模拟题 2016.8.27 [贪心] [DP] [计数问题]

20 篇文章 0 订阅
4 篇文章 0 订阅

LGTB 与偶数
LGTB 有一个长度为N 的序列。当序列中存在相邻的两个数的和为偶数的话,LGTB 就能把它们删掉。
LGTB 想让序列尽量短,请问能将序列缩短到几个数?
输入
第一行包含一个数N 代表序列初始长度
接下来一行包含N 个数a1, a2, …, aN,代表序列
对于50% 的数据,1 N 1000
对于100% 的数据,1 N 105, 0 ai 109
输出
输出包含一个数代表操作后序列最短长度
样例
样例输入样例输出
10
1 3 3 4 2 4 1 3 7 1
2
2


LGTB 与序列
LGTB 有一个长度为N 的序列A,现在他想构造一个新的长度为N 的序列B,使得B 中的任意两个数都
互质。
并且他要使ai与bi对应项之差最小
请输出最小值
输入
第一行包含一个数N 代表序列初始长度
接下来一行包含N 个数A1, A2, …, AN,代表序列A
对于40% 的数据,1 N 10
对于100% 的数据,1 N 100, 1 ai 30
输出
输出包含一行,代表最小值
样例
样例输入样例输出
51
6 4 2 8
3
样例说明
样例中B 数组可以为1 5 3 1 8
3


LGTB 与大数
LGTB 有一个非常大的数,并且他想对它进行Q 次操作
每次操作是把这个大数中的某种数字全部替换成一个数字串
他想知道Q 次操作之后得到的数对1000000007(109 + 7) 取模的结果,请输出给他
输入
输入第一行代表一个串S 代表初始的数
接下来一行有一个数字Q 代表操作次数
接下来Q 行,每行一个形如a->b1b2b3…bk 的串,代表将a 变成b1b2b3…bk
对于40% 的数据,1 jSj 1000,1 Q 10
对于100% 的数据,1 jSj 105,1 Q 105,询问中b 串的总长度不超过105
注意b 串可以为空
输出
输出包含一个数字,代表LGTB 想要的结果
样例
样例输入样例输出
123123
1
2->00
10031003
样例输入样例输出
222
2
2->0
0->7
777
4


T1:
容易发现对于可合并项,不同的合并顺序不会对答案有影响,那么可以用栈来维护,边读边处理(考试的时候没有想到用栈。。就手写了个链表来维护)。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<vector>
#include<queue>
#include<stack>
#include<map>
#include<set>
#include<string>
#include<iomanip>
#include<ctime>
#include<climits>
#include<cctype>
#include<algorithm>
#ifdef WIN32
#define AUTO "%I64d"
#else
#define AUTO "%lld"
#endif
using namespace std;
#define smax(x,tmp) x=max((x),(tmp))
#define smin(x,tmp) x=min((x),(tmp))
#define maxx(x1,x2,x3) max(max(x1,x2),x3)
#define minn(x1,x2,x3) min(min(x1,x2),x3)
const int INF=0x3f3f3f3f;
const int maxn = 100005;
struct Node
{
    int pre,next;
    int val;
}node[maxn];
#define pre(i) node[i].pre
#define next(i) node[i].next
#define val(i) node[i].val
int n;
int a[maxn];
inline void init()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++) scanf("%d",a+i);
    for(int i=1;i<=n;i++)
    {
        val(i)=a[i]&1;
        pre(i)=i-1;
        next(i)=i+1<=n?i+1:0;
    }
    next(0)=1;
}
int work()
{
    int last=0,now=1;
    while(now)
    {
        if(!last) last=now,now=next(now);
        if(!now) break;
        if(val(last)==val(now))
        {
            last=pre(last);
            now=next(now);
            next(last)=now;
            pre(now)=last;
        }
        else last=now,now=next(now);
    }
    int ret=0;
    int root=next(0);
    while(root)
    {
        ret++;
        root=next(root);
    }
    return ret;
}
int main()
{
    freopen("even.in","r",stdin);
    freopen("even.out","w",stdout);
    init();
    int ans=work();
    printf("%d",ans);
    return 0;
}

T2:
容易想到状压dp,但是却不容易想到优化。。。
首先如果原数列中Ai>Aj,那么最优答案一定是Bi>Bj的情况。
这样的话,由于原数不大于30,那么B数列中的数一定不超过58,这样那么如果让前面的大数先用大的Bi去匹配,然后剩下的用1去填,一定可以得到最优解。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<vector>
#include<queue>
#include<stack>
#include<map>
#include<set>
#include<string>
#include<iomanip>
#include<ctime>
#include<climits>
#include<cctype>
#include<algorithm>
#ifdef WIN32
#define AUTO "%I64d"
#else
#define AUTO "%lld"
#endif
using namespace std;
#define smax(x,tmp) x=max((x),(tmp))
#define smin(x,tmp) x=min((x),(tmp))
#define maxx(x1,x2,x3) max(max(x1,x2),x3)
#define minn(x1,x2,x3) min(min(x1,x2),x3)
const int INF=0x3f3f3f3f;
const int maxn = 105;
const int maxp = 17;
const int prime[] = {0,2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53}; // 16 primes
int n,a[maxn];
int dp[maxp][1<<maxp];
int status[maxn];
void init()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++) scanf("%d",a+i);
    sort(a+1,a+n+1,greater<int>());
    memset(status,0,sizeof(status));
    for(int i=1;i<=58;i++)
        for(int j=1;j<=16;j++)
            if(i<prime[j]) break;
            else if(i%prime[j]==0) status[i]|=(1<<j-1);
}
int dynamic()
{
    memset(dp,0x3f,sizeof(dp));
    dp[0][0]=0;
    for(int i=1;i<=min(n,16);i++) // current to fill in
        for(int S=0;S<(1<<16);S++) // last status
            for(int num=1;num<=58;num++)
                if(!(status[num]&S))
                    smin(dp[i][S|status[num]],dp[i-1][S]+abs(a[i]-num));
    int ans=INF;
    for(int S=0;S<(1<<16);S++)
        smin(ans,dp[min(n,16)][S]);
    return ans;
}
int main()
{
    freopen("seq.in","r",stdin);
    freopen("seq.out","w",stdout);
    init();
    int ans=dynamic();
    if(n>16) for(int i=17;i<=n;i++) ans+=abs(a[i]-1);
    printf("%d",ans);
    return 0;
}

T3:
这题竟然用DP!!!!!!!完全没看出来。。
要抓住每一次修改对相同字符所做的操作,在以后的任何询问时都是一样的序列,这样就可以用dp来实现记忆化,从而达到省时的目的。
那么可以构成类似于一棵树的样子,然后从底向上计算。
考虑子树合并。。
每一条串从后往前进行处理,一边处理数值一边维护数字长度,方便下一次计算。
除此之外,数的长度也要取模!!这样就可以用费马小定理了。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<vector>
#include<queue>
#include<stack>
#include<map>
#include<set>
#include<string>
#include<iomanip>
#include<ctime>
#include<climits>
#include<cctype>
#include<algorithm>
#ifdef WIN32
#define AUTO "%I64d"
#else
#define AUTO "%lld"
#endif
using namespace std;
#define smax(x,tmp) x=max((x),(tmp))
#define smin(x,tmp) x=min((x),(tmp))
#define maxx(x1,x2,x3) max(max(x1,x2),x3)
#define minn(x1,x2,x3) min(min(x1,x2),x3)
typedef long long LL;
const int maxn = 100005;
const int mod = 1000000007;
int quick_exp(int a,int p)
{
    if(!p) return 1;
    if(p==1) return a%mod;
    int tmp=quick_exp(a,p>>1);
    tmp=(LL)tmp*tmp%mod;
    if(p&1) return (LL)tmp*a%mod;
    else return tmp;
}
string s;
struct Query
{
    int val;
    string ss;
}que[maxn];
int q;
void init()
{
    cin>>s;
    scanf("%d",&q);
    for(int i=1;i<=q;i++)
    {
        char ch=getchar();
        while(!isdigit(ch)) ch=getchar();
        que[i].val=ch-'0';
        getchar();
        cin>>que[i].ss;
        que[i].ss.erase(0,1);
    }
}
#define maxbit 15
#define SIZE 9
int dp[maxn][maxbit],len[maxn][maxbit];
int dynamic()
{
    memset(len,0,sizeof(len));
    memset(dp,0,sizeof(dp));
    for(int i=0;i<=SIZE;i++) dp[q+1][i]=i,len[q+1][i]=1;
    for(int i=q;i>=1;i--)
        for(int j=0;j<=SIZE;j++)
            if(que[i].val^j)
            {
                dp[i][j]=dp[i+1][j];
                len[i][j]=len[i+1][j];
            }
            else
            {
                dp[i][j]=0; len[i][j]=0;
                int lens=que[i].ss.size();
                for(int p=lens-1;p>=0;p--)
                {
                    int to=que[i].ss[p]-'0';
                    dp[i][j]+=(LL)dp[i+1][to]*quick_exp(10,len[i][j])%mod;
                    if(dp[i][j]>=mod) dp[i][j]-=mod;
                    len[i][j]+=len[i+1][to]%(mod-1);
                    if(len[i][j]>=mod-1) len[i][j]-=mod-1; // Fermat Theory
                }
            }
    int lens=s.size();
    int d=0,l=0;
    for(int p=lens-1;p>=0;p--)
    {
        int to=s[p]-'0';
        d+=(LL)dp[1][to]*quick_exp(10,l)%mod;
        if(d>=mod) d-=mod;
        l+=len[1][to]%(mod-1);
        if(l>=mod-1) l-=mod-1;
    }
    return d;
}
int main()
{
    freopen("number.in","r",stdin);
    freopen("number.out","w",stdout);
    init();
    int ans = dynamic();
    printf("%d",ans);
    return 0;
}
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值