4.2-4.8练习

简单数学

Luogu P1372:1-n中找出k个数使得这k个数的gcd最大并求这个值。

要使x与另一个数的gcd尽量大,另一个数最好是x的倍数。即,答案序列应为x,x*2,x*3...x*k。又因为x*k不大于n,那么答案就是n/k


Luogu P1338:1-n的逆序数为m的字典序最小的排列。

若排列中已经确定了一些数字,那么对于剩下的i个位置,能够产生的最大逆序数是i*(i-1)/2。并且一个排列的逆序数越多,字典序就越大,那么如果m不比当前的最多逆序数大,就可以将最小数直接放在字典序最小的位置上也就是最前面的位置。否则,考虑最小数放置的位置,在长度为i的排列中一个数的位置改变最多可以产生i-1个逆序,要使字典序最小,就必须让剩下数字产生的逆序数最小,即此时将最小数放到最后一个位置上。

#include<cstdio>
#include<cstring>
#include<queue>
#include<vector>
#include<map>
#include<cmath>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#define maxn 1000050
#define INF 0x3f3f3f3f
#define eps 1e-8
using namespace std;
typedef long long ll;

ll n,m;
int a[maxn];

int main()
{
    scanf("%I64d%I64d",&n,&m);
    int l=1,r=n;
    for(int i=1;i<=n;i++)
    {
        ll tmp=(ll)(n-i)*(n-i-1)/2;
        if(m<=tmp)a[l++]=i;
        else
        {
            a[r--]=i;
            m-=(r-l+1);
        }
    }
    for(int i=1;i<=n;i++)
        printf(i==n?"%d\n":"%d ",a[i]);
    return 0;
}

Luogu P2158:n*n方阵的左下角看去,问能看到多少个人。

有三个特殊点(1,0),(1,1),(0,1),其余的点要想被看到,可知坐标(x,y)需满足xy互质。

即:ans=3+2*phi(i) i:2->n-1

#include<cstdio>
#include<cstring>
#include<queue>
#include<vector>
#include<map>
#include<cmath>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#define maxn 40050
#define INF 0x3f3f3f3f
#define eps 1e-8
using namespace std;
typedef long long ll;

int n;
int phi[maxn];

void init()
{
    phi[1]=1;
    for(int i=2;i<=n;i++)
    {
        if(!phi[i])
        {
            for(int j=i;j<=n;j+=i)
            {
                if(!phi[j])phi[j]=j;
                phi[j]=phi[j]*(i-1)/i;
            }
        }
    }
}

int main()
{
    scanf("%d",&n);
    if(n==1){printf("0\n");return 0;}
    init();
    int ans=0;
    for(int i=2;i<n;i++)
        ans+=phi[i];
    printf("%d\n",2*ans+3);
    return 0;
}

Luogu P1582n堆初始为1,每次将两堆相同的合并,现在需要合并到不超过k堆,问最少需要增加多少堆。

n堆,最终合并的情况是和n的各位二进制相对应的。那么就每次加上lowbit(n),直至二进制中1的个数不超过k为止。

#include<cstdio>
#include<cstring>
#include<queue>
#include<vector>
#include<map>
#include<cmath>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#define maxn 40050
#define INF 0x3f3f3f3f
#define eps 1e-8
using namespace std;
typedef long long ll;

int n,k;

int num(int x)
{
    int tmp=x;
    int ans=0;
    while(tmp)
    {
        ans+=(tmp%2);
        tmp/=2;
    }
    return ans;
}
int lowbit(int x){return x&-x;}

int main()
{
    scanf("%d%d",&n,&k);
    int ans=0;
    while(1)
    {
        int cnt=num(n);
        if(cnt<=k)break;
        ans+=lowbit(n);
        n+=lowbit(n);
    }
    printf("%d\n",ans);
    return 0;
}

二维dp

Luogu1387:给出一个n*m01矩阵,问最大的不含0的正方形的边长是多少。

在将矩阵扫一遍的过程中,在左方、上方、左上方中取最小值即可。

状态转移方程dp[i][j]=min(dp[i-1][j-1],dp[i][j-1],dp[i-1][j])+1,其中要求a[i][j]1

#include<cstdio>
#include<cstring>
#include<queue>
#include<vector>
#include<map>
#include<cmath>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#define maxn 105
#define INF 0x3f3f3f3f
#define eps 1e-8
using namespace std;
typedef long long ll;

int n,m;
int dp[maxn][maxn];
int a[maxn][maxn];

int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=m;j++)
        {
            scanf("%d",&a[i][j]);
        }
    }
    memset(dp,0,sizeof(dp));
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=m;j++)
        {
            if(a[i][j]==0)
                continue;
            int tmp=min(dp[i-1][j-1],min(dp[i][j-1],dp[i-1][j]));
            dp[i][j]=tmp+1;
        }
    }
    int ans=0;
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=m;j++)
            ans=max(ans,dp[i][j]);
    }
    printf("%d\n",ans);
    return 0;
}

Luogu1736:给定一个01矩阵,问最大的仅对角线为1的正方形的边长是多少。

和上一个很像,只不过需要一个预处理。

S1[i][j]表示最多从(i,j)向左/右延伸多少个格子,使得这些格子都为0S2表示向上的相同含义。进行预处理之后,状态转移方程为dp[i][j]=min(dp[i-1][j-1],s1[i][j-1],s2[i-1][j])+1

对角线有可能是向左或者向右的,因此需要正着扫一遍再倒着扫一遍。

#include<cstdio>
#include<cstring>
#include<queue>
#include<vector>
#include<map>
#include<cmath>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#define maxn 2505
#define INF 0x3f3f3f3f
#define eps 1e-8
using namespace std;
typedef long long ll;

int n,m;
int a[maxn][maxn];
int dp[maxn][maxn],s[maxn][maxn],t[maxn][maxn];

int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=m;j++)
            scanf("%d",&a[i][j]);
    }
    int ans=0;
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=m;j++)
        {
            if(!a[i][j])
            {
                s[i][j]=s[i][j-1]+1;
                t[i][j]=t[i-1][j]+1;
            }
            if(a[i][j])
                dp[i][j]=min(dp[i-1][j-1],min(s[i][j-1],t[i-1][j]))+1;
            ans=max(ans,dp[i][j]);
        }
    }
    memset(dp,0,sizeof(dp));
    memset(s,0,sizeof(s));
    for(int i=1;i<=n;i++)
    {
        for(int j=m;j>=1;j--)
        {
            if(!a[i][j])
                s[i][j]=s[i][j+1]+1;
            if(a[i][j])
                dp[i][j]=min(dp[i-1][j+1],min(s[i][j+1],t[i-1][j]))+1;
            ans=max(ans,dp[i][j]);
        }
    }
    printf("%d\n",ans);
    return 0;
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值