2018.8.16提高B组模拟考试

永远不要把时间交给一道打表题。

 

T1 题意简述:jzoj4674

 

Description

科学家温斯顿定义了一个无限重复的数列:1234321234321234321……,并将其称为时钟序列。
他发现可以将数列人为分成几段:
1, 2, 3, 4, 32, 123, 43, 2123, 432, 1234, 32123, ...
他又定义了新数列中第n项为Vn,这样分组能够满足Vn的数字和等于n。例如,V2=2,V7=43,V11=32123。
请帮助他求出数列V的前n项和。

Input

第一行一个正整数,表示n。

Output

第一行一个整数,表示数列V前n项和对123454321取模后的值。

Data Constraint

Subtask1[20pts]:N<=40
Subtask2[20pts]:N<=1000
Subtask3[60pts]:N<=10^14

 

   解题思路:emmm打表。

             首先登陆OEIS查询算出数列的前几项,发现数列每15项有如下规律:

             1 ~15:A

             16~30:AB

             31~45:ABB

             因此可以把n先分解为a*15+b,然后分别计算。

             A与B的前缀和都可以打表算出,系数可以用等比数列求和公式推出。需要逆元。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<algorithm>
#define ll long long
#define MOD 123454321
#define ny 96007682
using namespace std;
ll n,ans,a,b;
ll sum1[16]={0,1,3,6,10,42,165,208,2331,2763,3997,36120,79332,113653,137085,260517};
ll sum2[16]={0,234321,577533,1009656,1330890,1454322,1886445,2098788,2530911,2654343,2975577,3407700,3750912,3985233,4108665,4232097};
//ll a[51]={0,
//1      ,2      ,3      ,4      ,32      ,123      ,43      ,2123      ,432      ,1234      ,32123      ,43212      ,34321      ,23432      ,123432      ,
//1234321,2343212,3432123,4321234,32123432,123432123,43212343,2123432123,432123432,1234321234,32123432123,43212343212,34321234321,23432123432,123432123432};
ll qpow(ll x,ll y)
{
    ll ans=1;
    while(y)
    {
        if(y&1) ans=ans*x%MOD;
        x=x*x%MOD;
        y/=2;
    }
    return ans;
}
ll gcd(ll x,ll y)
{
    return (y? gcd(y,x%y):x);
}
int main()
{
    scanf("%lld",&n);
    a=n/15,b=n%15;
    ll a1=qpow(1000000,a);
    a1--;a1=a1*ny%MOD;
    ll a2=a1-a;
    while(a2<0) a2+=MOD;
    a2=a2*ny%MOD;
    ll a4=a1;
    ll a3=qpow(1000000,a+1);
    a3--;a3=a3*ny%MOD;
    a3=(a3-a1+MOD)%MOD;
    ans=(a1*sum1[15]%MOD+a2*sum2[15]%MOD+a3*sum1[b]%MOD+a4*sum2[b]%MOD)%MOD;
    printf("%lld\n",ans%MOD);
    return 0;
}

 


 

T2 题意简述:jzoj1321

 

Description

  贝希和她的闺密们在她们的牛棚中玩游戏。但是天不从人愿,突然,牛棚的电源跳闸了,所有的灯都被关闭了。贝希是一个很胆小的女生,在伸手不见拇指的无尽的黑暗中,她感到惊恐,痛苦与绝望。她希望您能够帮帮她,把所有的灯都给重新开起来!她才能继续快乐地跟她的闺密们继续玩游戏!
  牛棚中一共有N(1 <= N <= 35)盏灯,编号为1到N。这些灯被置于一个非常复杂的网络之中。有M(1 <= M <= 595)条很神奇的无向边,每条边连接两盏灯。
  每盏灯上面都带有一个开关。当按下某一盏灯的开关的时候,这盏灯本身,还有所有有边连向这盏灯的灯的状态都会被改变。状态改变指的是:当一盏灯是开着的时候,这盏灯被关掉;当一盏灯是关着的时候,这盏灯被打开。
  问最少要按下多少个开关,才能把所有的灯都给重新打开。
  数据保证至少有一种按开关的方案,使得所有的灯都被重新打开。

Input

  第一行:两个空格隔开的整数:N和M。

  第二到第M+1行:每一行有两个由空格隔开的整数,表示两盏灯被一条无向边连接在一起。没有一条边会出现两次。

Output

  第一行:一个单独的整数,表示要把所有的灯都打开时,最少需要按下的开关的数目。

 

   解题思路:布鲁特佛斯算法。(滑稽)

             (注:布鲁特佛斯 = Brute Force = 暴力)

             注意到n只有35,非常小。可以考虑枚举。

             但是2^35是肯定要超时的。考虑折半搜索(meet in the middle)。

             只需把前一半的所有情况枚举出来,存在vector内,再枚举后一半,二分查找vector内是否

             有与当前情况异或起来等于(1<<n)-1的元素。有则更新答案。

             为了方便调试,我加了一个用于去重的map。不加应该也可以。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<algorithm>
#include<vector>
#include<map>
#define INF 0x3f3f3f3f
#define ll long long
using namespace std;
ll n,m,ans=INF,mid,cnt,tot,head[40],vis[40],pos[40];
struct uio{
    ll nxt,to;
}edge[1200];
struct oiu{
    ll num,pos;
};
vector<oiu> vec;
map<ll,ll> mp;
struct cmp{
    bool operator () (const oiu &a,const oiu &b) const{
        return a.pos<b.pos;
    }
};
void add(ll x,ll y)
{
    edge[++cnt].nxt=head[x];
    edge[cnt].to=y;
    head[x]=cnt;
}
void dfs(ll x,ll num,ll p)
{
    if(!mp[p]) vec.push_back({num,p}),mp[p]=vec.size();
    else vec[mp[num]].num=min(vec[mp[num]].num,num);
    if(x==mid+1ll) return;
    dfs(x+1ll,num,p);
    dfs(x+1ll,num+1ll,p^pos[x]);
}
void dfs1(ll x,ll num,ll p)
{
    oiu tmp={num,p^((1ll<<n)-1ll)};
    vector<oiu>::iterator it=lower_bound(vec.begin(),vec.end(),tmp,cmp());
    if(it!=vec.end())
        if(((*it).pos^p)==((1ll<<n)-1ll)) ans=min(ans,(*it).num+num);
    if(x==n+1ll) return;
    dfs1(x+1ll,num,p);
    dfs1(x+1ll,num+1ll,p^pos[x]);
}
int main()
{
    scanf("%lld%lld",&n,&m);
    mid=(1ll+n)>>1ll;
    for(ll i=1ll;i<=m;i++)
    {
        ll u,v;
        scanf("%lld%lld",&u,&v);
        add(u,v),add(v,u);
    }
    for(ll i=1ll;i<=n;i++)
    {
        for(ll j=head[i];j;j=edge[j].nxt) vis[edge[j].to]=1ll;
        vis[i]=1ll;ll k=0;
        for(ll j=1ll;j<(1ll<<n);j<<=1ll){k++;if(vis[k]) pos[i]+=j,vis[k]=0;}
    }
    dfs(1ll,0,0);
    sort(vec.begin(),vec.end(),cmp());
//    for(ll i=0;i<vec.size();i++)
//        printf("%lld %lld\n",vec[i].pos,vec[i].num);
    dfs1(mid+1ll,0,0);
    printf("%lld\n",ans);
    return 0;
}

 


 

T3 题意简述:jzoj1322

 

Description

  FJ的奶牛喜欢玩硬币游戏,所以FJ发明了一个新的硬币游戏。一开始有N(5<=N<=2,000)个硬币堆成一叠,从上往下数第i个硬币有一个整数值C_i(1<=C_i<=100,000)。
  两个玩家轮流从上倒下取硬币,玩家1先取,可以从上面取1个或2个硬币,下一轮的玩家可以取的硬币数量最少为1个,最多为上一个玩家取的数量的2倍,硬币全部取完比赛结束。
  已知玩家2绝顶聪明,会采用最优策略,现在请你帮助玩家1,使得玩家1取得的硬币值的和最大。

Input

  第一行输入N
  第二至N+1行每行输入一个整数C_i

Output

  输出玩家1能获得的最大值。

 

   解题思路:dp。

             设dp[i][j]为剩余i个硬币,上一轮对手取了j个硬币的最大收益。

             dp[i][j]=max(sum(i-k+1,i)+sum(1,i-k)-dp[i-k][k])。

             sum(i-k+1,i)表示本轮自己取了从i-k+1到i共k枚硬币。

             sum(1,i-k)-dp[i-k][k]表示下一轮对手取完后自己最多还能取多少硬币。

             发现转移是O(n^3)的,考虑化简。

             发现dp[i][j]可以由dp[i][j-1]转移而来,方程如代码中所示。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<algorithm>
using namespace std;
int n,sum[2001],dp[2001][2001];
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
        scanf("%d",&sum[n-i+1]);
    for(int i=1;i<=n;i++) sum[i]+=sum[i-1];
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n-i+1;j++)
        {
            dp[i][j]=dp[i][j-1];
            int k=2*j-1;
            if(i>=k) dp[i][j]=max(dp[i][j],sum[i]-dp[i-k][k]);
            if(i>=++k) dp[i][j]=max(dp[i][j],sum[i]-dp[i-k][k]);
        }
//            for(int k=1;k<=2*j&&k<=i;k++)
//                dp[i][j]=max(dp[i][j],sum[i]-dp[i-k][k]);
    printf("%d\n",dp[n][1]);
    return 0;
}

 

posted @ 2018-08-16 21:51  radishえらい  阅读(306)  评论(0编辑  收藏  举报