集训Day6

Day·6


看到题目难受的虚伪了


Problem·1

题面
给出一张只有1和0的图,可以交换任意两列组成一个以一为元素的矩阵,求最大形成的矩阵中有几个1
思路

第一眼,觉得应该是DP于是果断放弃,关键是DP做的实在是太少了………………难受死了

题解

部分分:考虑矩阵的上下边界,求第i-j行全为1的列的数量,暴力判断N^3按顺序枚举N^2
仅枚举矩阵的下边界,递推维护每个数字向上连续1的个数。
有意义的上边界仅有M个(向上连续1终止的位置)。
维护这些上边界:下边界i-1到i时,上边界要么+1,要么重置为i。+1的上边界相对大小不改变,稍加维护即可。复杂度O(N*M)。

代码
#include<bits/stdc++.h>  
using namespace std; 
int n,m;
char z[1530];
int a[1530],t[1530];
int p[1530];
int ans=0;
int main()  
{    
    freopen("logs.in","r",stdin);
    freopen("logs.out","w",stdout);
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++)t[i]=i;
    for(int i=1;i<=n;i++){
        scanf("%s",&z[1]);
        for(int j=1;j<=m;j++)p[j]=0;
        for(int j=1;j<=m;j++){
            if(z[j]=='1'){

            }
            else
                p[t[j]]=1;
        }
        for(int j=1;j<=m;j++)p[j]+=p[j-1];
        int tt=m-p[m]+1;
        for(int j=1;j<=m;j++){
            if(z[j]=='1'){
                t[j]-=p[t[j]];
                a[j]++;
                ans=max(ans,a[j]*t[j]);
            }
            else
                t[j]=tt++,a[j]=0;
        }
    }
    cout<<ans<<endl;
    fclose(stdin);
    fclose(stdout);
    return 0;  
}  


Problem·2

题面
给出两个数,m和p,求以p为最小质因子的第m个数,如果大于10^18输出0,保证p为质数
思路

拿到题想错了,想成了最小的数也得满足p^m,但是没有考虑到质数相乘会小于p^m的情况,比个例子,m=3,p=3,我原以为ans满足>=3^3实际上3*5也满足了这个条件而不是单纯的p的次幂,于是难过,想错了就不想做了,byebye第二题,我要虚伪了。

题解

对大P,小P分别求解。
P比较大时,P在10^9以内倍数不多,求一个质数表,p乘以第n-1个大于等于P的质数 复杂度10^9/P
P比较小时,二分答案,对小于P的质数进行容斥。设t=小于p质数个数。复杂度 log(10^9)*(2^t)*t

代码
#include <cstdio>
#include <cstring>
#include <vector>

using namespace std;

typedef long long llint;
const int C = 50;
const int MAX = 1000000000;

llint f(int mid, int p) {

    vector<int> prime, v(p, 0);
    for (int i = 2; i < p; ++i)
        if (!v[i]) {
            prime.push_back(i);
            for (int j = i + i; j < p; j += i)
                v[j] = 1;
        }

    llint ret = mid;
    for (int mask = 1; mask < (1 << (int) prime.size()); ++mask) {
        llint d = 1, sgn = -1;

        for (int i = 0; i < prime.size(); ++i)
            if (mask & (1 << i)) {
                d *= prime[i];
                if (d > mid) break;
                sgn *= -1;
            }

        ret -= sgn * mid / d;
    }

    return ret;
}

void solve_small(int n, int p) {
  int ans = 0;

  int lo = 1;
  int hi = MAX / p;

  while (lo < hi) {
    int mid = lo + (hi - lo) / 2;    
    long long k = f(mid, p);
    if (k < n)
      lo = mid + 1;
    else
      hi = mid;
  }

  if (f(lo, p) == n)
    ans = lo * p;

  printf("%d\n", ans);
}

void solve_large(int n, int p) {
  int ans = 0;
  if (n == 1) 
    ans = p;

  int len = MAX / p + 1;
  char* v = new char[len];
  memset(v, 0, len);

  for (int i = 2, k = 1; i < len; ++i)
    if (!v[i]) {
      if (i < p)
        for (llint j = (llint) i * i; j < len; j += i)
          v[j] = 1;
      else {
        ++k;
        if (k == n)
          ans = i * p;
      }
    }

  delete[] v;
  printf("%d\n", ans);
}

int main(void) {
    freopen("broj.in","r",stdin);
    freopen("broj.out","w",stdout);
    int n, p;
    scanf("%d %d", &n, &p);

    if (p < C)
        solve_small(n, p);
    else 
        solve_large(n, p);
    fclose(stdin);
    fclose(stdout);
    return 0;
}

Problem·3

题面
操场上有N个人,你要帮助他们组成小组。每个人有一个愿望值Ai,表示自己所在小组不少Ai人。求在满足最多人愿望的情况下,最多能划分出几个小组。在满足最多人愿望且划分小组个数最多的情况下,最小化最大小组内的人数。求小组数和最大小组的人数。
思路

虚伪之力………………

题解

对每个人的需求A_i从大到小排序。
初步思考:考虑满足第一个人的需求,暂时选择前A_1个人组成一个小组。理由:之后的人的需求弱于之前的人,因此前A_1个人组成一组一定合法。把这A_1个人跟其余人交换,不会更优。
考虑第A_1+1个人,这个人要么加入前A_1个人的小组,要么自成一组且成为小组最强需求。。。。。
第一问解法:
设f[i]表示前i个人构成的最多小组数量。
加入:若f[i-1]>0即已有一些小组,那么第i个人可以加入之前的小组。f[i]可以从f[i-1]转移。
构成:k-1+A_k==i即k~i个人正好构成以A_k为上限的小组。
若枚举复杂度较大。
考虑到k-1+A_k只有n个值,可以预先更新。即从小到大计算f,计算f[k-1]时更新f[i]。
第二问解法:
同上考虑,设g[i]表示前i个人构成最多小组时,最小可能取到的最大小组人数。
若i加入之前小组,则g[i]从g[i-1]转移。(注意考虑是否每组都达到最大人数)
若构成小组,g[i]从g[k-1]转移。K可能有多个,但总数不超过n。
总复杂度O(n)

代码
#include <bits/stdc++.h>
using namespace std;
int n;
int a[1000005];
int f[1000005],g[1000005];
int main() {
    freopen("team.in","r",stdin);
    freopen("team.out","w",stdout);
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
        scanf("%d",&a[i]);
    sort(&a[1],&a[n+1]);
    for(int i=1;i<=n;i++)f[i]=-n*2,g[i]=n*2;
    for(int i=n;i>=1;i--){
        int x=max(0,i-a[i]+1);
        if(f[i+1]>=0){
            if(f[x]<f[i+1]+1){
                f[x]=f[i+1]+1;
                g[x]=max(g[i+1],a[i]);
            }
            else
                if(f[x]==f[i+1]+1){
                    g[x]=min(g[x],max(g[i+1],a[i]));
                }
        }
        if(f[i+1]>0){
            if(f[i]<f[i+1]){
                f[i]=f[i+1];
                g[i]=g[i+1]+(n-i==g[i+1]*f[i+1]);
            }
            else
                if(f[i]==f[i+1]){
                    g[i]=min(g[i],g[i+1]+(n-i==g[i+1]*f[i+1]));
                }
        }
    }
    printf("%d\n%d\n",f[1],g[1]);
    fclose(stdin);
    fclose(stdout);
    return 0;
}

上课的内容

讲了DP,嗯,很重要………………我觉得关键还是多练吧
给个几道模板,《玩具装箱(斜率优化)》,

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值