【长沙集训】2017.9.19

网上的题。

T1

币 (coin)

1.1题目描述

有一排硬币堆,两个人轮流取硬币。每个选手随机取最左边或者最右边的一堆硬币。求先手期 望取得的硬币数。

1.2 输入格式

本题有多组测试数据。

第一行一个数 T ,表示数据组数。

对于每组测试数据,第一行一个正整数 n ,表示有多少堆硬币。

第二行 n 个非负整数,依次表示每一堆硬币的个数。

1.3 输出格式

对于每组测试数据,输出一行一个数,表示先手期望取得的硬币数。

保留 3 位小数。

1.4 样例输入

2

3

1 4 9

4

5 5 5 5

1.5 样例输出

9.500

10.000

1.7 数据范围

对于 30% 的数据:n ≤ 10, T ≤ 5

对于 60% 的数据:T ≤ 10

对于 100% 的数据:n ≤ 1000, T ≤ 1000 ,每一堆的个数 ≤ 10^3

     

第一眼看到期望DP感觉十分难受,然后看了看60分的n^2暴力还是挺好想的,dp[i][j][1]表示从从i到j先手期望得到的金币,dp[i][j][0]表示后手的期望,转移大概这样

    for(int i=1;i<n;i++)
            for(int j=1;j<=n&&j+i<=n;j++) {
                dp[j][j+i][1]=0.5*(dp[j][j+i-1][0]+v[j+i])+0.5*(dp[j+1][j+i][0]+v[j]);
                dp[j][j+i][0]=0.5*(dp[j][j+i-1][1]+dp[j+1][j+i][1]);
            }

算了下复杂度有5*10^8,还天真地以为能卡过。。

正解是,考虑数据组数很多,必须优化。对于特定的长度,第i堆金币取的概率和数量无关,预处理出这个概率然后每次直接乘就好了。

dp[i][j]表示长度为i的数列先手能取到第j个的概率。转移非常优美

for(int i=2;i<=1000;i++) {
        dp[i][1]=1-dp[i-1][1]*0.5;
        for(int j=2;j<i;j++)
            dp[i][j]=1-0.5*(dp[i-1][j]+dp[i-1][j-1]);
        dp[i][i]=1-dp[i-1][i-1]*0.5;
    }

转移考虑的是减去取不到的概率。

 第一个数先手取不到的概率为先手这一轮不取它在而取最后一个的概率(0.5)乘上下一轮游戏中(i-1个数)后手作为那一轮的先手能取到第一个的概率;

 本轮第j个数取不到的概率为本轮取最后一个,下一轮后手作为先手能取到它的第j个(0.5*dp[i-1][j])加上本轮取第一个,下一轮后手作为先手能取到它的第j-1个(此轮的第j个)的概率(0.5*dp[i-1][j-1];

最后一个数取不到的概率同理。

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<queue>
#include<vector>
typedef long long LL;
const int maxn=1000+29;
int T,n;
double ans,dp[maxn][maxn],v[maxn];
using namespace std;
int main()
{
    //freopen("coin.in","r",stdin);
    //freopen("coin.out","w",stdout);
    scanf("%d",&T);
    dp[1][1]=1;
    for(int i=2;i<=1000;i++) {
        dp[i][1]=1-dp[i-1][1]*0.5;
        for(int j=2;j<i;j++)
            dp[i][j]=1-0.5*(dp[i-1][j]+dp[i-1][j-1]);
        dp[i][i]=1-dp[i-1][i-1]*0.5;
    }
    while(T--) {
        scanf("%d",&n);
        ans=0;
        for(int i=1;i<=n;i++) { scanf("%lf",&v[i]); ans+=v[i]*dp[n][i];}
        printf("%.3lf\n",ans);
    }
    return 0;
}
AC

 

 

T2

∆ (triangle)

2.1 题目描述

给定一个无自环重边的无向图,求这个图的三元环1的个数以及补图2的三元环个数。

2.2 输入格式

第一行 2 个数 n, m ,分别表示图的点数、边数。

接下来 m 行,每行两个数 u, v ,表示一条连接 u, v 的无向边。

2.3 输出格式

一行两个数,依次表示原图的三元环个数以及补图的三元环的个数。

2.4 样例输入

5 5

1 2

1 3

2 3

2 4

3 4

2.5样例输出

2 1

2.6数据范围

对于 30% 的数据:n ≤ 100

对于 60% 的数据:m ≤ 500

对于 100% 的数据:n ≤ 10^5 , m ≤ 10^5

2.7评分方式

如果你两个数均输出正确,得 10 分。

否则如果两个数中任意一个正确或者两个数的和正确,得 6 分。 否则不得分。

      引用LLJ大佬的经典名句之:我觉得这道题很妙。

      先说在我们知道了图的答案如何求补图的答案。不知道长沙大佬讲的是不是这个,长沙话实在不太听得懂。

      SXY大佬的做法,通过在下——灵魂画手展现在图中

     然后只要求出原图的答案就好了啊。可以证明,暴力可做。

     如果一个点的出度k<sqrt(m),它连向的点出度最多为m,暴力复杂度为m*sqrt(m);

     如果一个点的出度k>=sqrt(m),因为只有m条边,所以连向的每个点最多平均每个点sqrt(m)条边,复杂度为m*sqrt(m);

     所以就可以直接暴力了呀。

     然后可能是我纯粹的暴力不太优美,必须加个读优然后把不需要开LL的地方改成INT才能卡进时限,本校oj测的,可能实际会T吧,听大佬们说要用hash什么的;

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<queue>
#include<vector>
typedef long long LL;
using namespace std;
const int maxn=1e5+299;
int n,m,u,v,fir[maxn],nxt[maxn*2],to[maxn*2],out[maxn],vis[maxn],ecnt;
LL A,B,C,a,b,c,d;
void add(int u,int v) {
    nxt[++ecnt]=fir[u]; fir[u]=ecnt; to[ecnt]=v; out[u]++;
    nxt[++ecnt]=fir[v]; fir[v]=ecnt; to[ecnt]=u; out[v]++;
}
inline int read() {
    int res=0; char ch=getchar();
    while(ch>'9'||ch<'0') ch=getchar();
    for(;ch>='0'&&ch<='9';ch=getchar()) res=res*10+ch-'0';
    return res;
}
int main()
{
    //freopen("triangle.in","r",stdin);
    //freopen("triangle.out","w",stdout);
    //scanf("%lld%lld",&n,&m);
    n=read(); m=read();
    for(int i=1;i<=m;i++) {
        //scanf("%lld%lld",&u,&v);
        u=read(); v=read();
        add(u,v);
    }
    for(int i=1;i<=n;i++) {
        LL k=(LL)out[i];
        A+=k*(k-1)/2;
        B+=(n-k-1)*(n-k-2)/2;
        C+=k*(n-k-1);
        for(int j=fir[i];j;j=nxt[j]) 
            vis[to[j]]=i;
        LL tmp=0;
        for(int j=fir[i];j;j=nxt[j]) {
            int x=to[j];
            for(int k=fir[x];k;k=nxt[k]) {
                    if(vis[to[k]]==i) tmp++;
                }
             }
         a+=tmp/2;
    }
    a/=3;
    b=A-3*a;
    c=(C-2*b)/2;
    d=(B-c)/3;
    printf("%lld %lld\n",a,d);
    return 0;
}
code

 

           

T3

数(aqnum)

3.1 题目描述

秋锅对数论很感兴趣,他特别喜欢一种数字。秋锅把这种数字命名为 农数 ,英文名为 AQ number 。

这种数字定义如下:

定义 1 一个数 n 是农数,当且仅当对于每个质数 p ,要么 p ∤ n ,要么 p ≤ MAXPRIME

且存在一个 正奇数 k 使得 p k | n 且 p k+1 ∤ n 。

秋锅想知道,给定 N,MAXPRIME ,问 1 到 N 里面的农数有多少个呢?

3.2 输入格式

一行 2 个数,

分别为 N 和 MAXPRIME 。

3.3 输出格式

一行一个数,表示 1 到 N 中农数的个数。

3.4 样例输入

10 3

3.5 样例输出

5

3.6数据范围

对于 30% 的数据:N ≤ 1000

对于 60% 的数据:N ≤ 5 × 10^6

对于 100% 的数据:N ≤ 10^10 ,MAXPRIME ≤ 10^6

      一道神题。爆搜加一个剪枝。

       60分做法可以直接预处理的时候处理每个数的最大质因数,然后枚举每个点判断是否是农数。

       考场上觉得60分贼稳结果数组开小了GG,直接成爆搜的30分。。。

      正解是爆搜的基础上,如果搜到一个质数当前的now*p[cnt]^2>n了,就在这之后的质数中二分一个最大的p[k]使p[k]*now<=n,然后ans+=k-cnt+1;

      正确性显然,时间复杂度不会算,反正在下觉得很神。

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<queue>
#include<vector>
typedef long long LL;
const int maxn=2e6+299;
using namespace std;
LL p[maxn],bo[maxn],mp;
LL n,ans;
void get_prime() {
    for(int i=2;i<=mp;i++) {
        if(!bo[i]) p[++p[0]]=i;
        for(int j=1;j<=p[0]&&p[j]*i<=mp;j++) {
            bo[i*p[j]]=1;
            if(i%p[j]==0) break;
        }
    }
}
int ef(int l,int r,LL now) {
    int res=-1;
    while(l<=r) {
        int mid=(l+r)>>1;
        if(p[mid]*now<=n) res=mid,l=mid+1;
        else r=mid-1;
    }
    return res;
}
void dfs(int cnt,LL now) {
    if(now>n) return;
    if(cnt==p[0]+1) {ans++; return;}
    LL tp=now;
    if(now*p[cnt]*p[cnt]>n) {
        int kk=ef(cnt,p[0],now);
        if(kk!=-1) ans+=kk-cnt+1;
        ans++;
        return ;
    }
    for(int i=0;i<=30;i++) {
        if(tp>n) break;
        dfs(cnt+1,tp);
        if(i==0) tp*=p[cnt];
        else tp*=p[cnt]*p[cnt];
    }
}
int main()
{
    //freopen("aqnum.in","r",stdin);
    //freopen("aqnum.out","w",stdout);
    scanf("%lld%d",&n,&mp);
    get_prime();
    dfs(1,1LL);
    printf("%lld\n",ans);
    return 0;
}
View Code

 

 

再次坚定信仰,始终坚信暴力出奇迹。

 

posted @ 2017-09-19 17:40  啊宸  阅读(354)  评论(1编辑  收藏  举报