JZOJ 8.12 B组总结

NO.1 牛棚的安排

Description

FJ的N(1<=N<=50,000)头奶牛实在是太难伺候了,她们甚至有自己独特的产奶时段。当然对于某一头奶牛,她每天的产奶时段是固定的,为时间段A..B(1<=A<=B<=1,000,000),包括时间段A和时间段B。显然,FJ必须开发一个调控系统来决定每头奶牛应该被安排到哪个牛棚去挤奶,因为奶牛们显然不希望在挤奶时被其它奶牛看见。
FJ希望你帮他计算一下:
如果要满足奶牛们的要求,并且每天每头奶牛都要被挤过奶,至少需要多少牛棚

Input

第1行: 一个单独的整数N,为奶牛的总数
第2..N+1行: 每行包括2个用空格隔开的正整数,第i+1行的数据描述的是第i头奶牛的产奶时段

Output

第1行: 输出一个整数M,表示最少需要的牛棚数

Sample Input

5
1 10
2 4
3 6
5 8
4 7

Sample Output

4


思路:堆+快排
这题其实就是求n-有多少个没有覆盖的区间
我们可以先将开始时间进行多关键字排序
然后我们维护一个堆,让右端点较小的在前面
每次将堆首提出,如果堆首不能满足当前区间,则将当前区间打入堆
如果可以,则将堆首的右端点更新
再维护堆


代码:

#include
#include
#include
#include
#include
using namespace std;
int s[50000+10],t[50000+10],rank[50000+10],up[50000+10];
bool cmp1(int a,int b){return s[a]<=s[b];}
struct cmp2
{
    bool operator()(int a,int b)
    {
        return t[a]>=t[b];
    }  
};
priority_queue<int,std::vector<int>,cmp2>D;
int main()
{
    freopen("reserve.in","r",stdin);
    freopen("reserve.out","w",stdout);
    int cnt = 0;
    int n;
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        scanf("%d%d",&s[i],&t[i]);
        rank[i]=i;
    }
    sort(rank+1,rank+1+n,cmp1);
    D.push(rank[1]);
    cnt++;
    for(int i=2;i<=n;i++)
    {
        int top=D.top();
        if(s[rank[i]]>t[top])
        {
            D.pop();
            D.push(rank[i]);
        }
        else D.push(rank[i]),cnt++;
    }
    printf("%d\n",cnt);
    return 0;
}

NO.2 数字游戏

Description

Charles和sunny在玩一个简单的游戏。若给出1~n的一个排列A,则将A1、A2相加,A2、A3相加……An-1、An相加,则得到一组n-1个元素的数列B;再将B1、B2相加,B2、B3相加,Bn-2、Bn-1相加,则得到一组n-2个元素的数列……如此往复,最终会得出一个数T。而Charles和sunny玩的游戏便是,Charles给出n和T,sunny在尽可能短的时间内,找到能通过上述操作得到T且字典序最小的1~n的排列。(sunny大声说:“What an easy game!”,接着几下就给出了解),Charles觉得没意思,就想和你玩,当然,你可以用一种叫做“电子计算机”的东西帮你。

Input

本题有多组数据,对于每组数据:一行两个整数n(0< n<=20),t即最后求出来的数。两个0表示输入结束。

Output

对于每组测试数据输出一行n个整数,用空格分开,行尾无多余空格,表示求出来的满足要求的1~n的一个排列。

Sample Input

4 16
3 9
0 0

Sample Output

3 1 2 4
1 3 2


思路:搜索+一堆剪枝
题目要求1-N的一个排列A1,A2…An使得C(N-1,0)*A1+C(N-1,1)*A2+….+C(N-1,N-1)*AN=T,
即: 这里写图片描述—–①式
方法很好确定,先把C(N-1,i)求出来,然后只要把每一个位上的数确定好就可以了,所以采用深度优先搜索的方法。
方法:直接搜,用DFS(x,y)表示当前将要确定第x个位置上数,已经确定的和为y,把每种情况都搜出来,然后在递归出口的位置判断①式是否成立,不过很可惜,这种方法得不到分;
剪枝一:加一个小小的优化,就是在确定第X个数时,保证新求出来的y不能大于T,加上这个优化后,可以得40分;
剪枝二:由于C(N-1,i)=C(N-1,N-1-i),具有对称性,题目又要求最小字典序列,所以在枚举时当x>n div 2 时,要保证a[x]>a[N+1-x],这样程序速度会提高,但是该题还是只能得40分;
剪枝三:当枚举到第X个数时,剩余的N-X个数,与剩余的N-X个系数一一对应,让大数和大系数相乘,小数和小系数相乘可以得到最大值,让大数和小系数相乘,小数和大系数相乘可以得到最小值,如果剩余的值不在这个范围内,就不要搜下去,这样可以大大优化


代码:

var b,c,d:array[0..100]of longint;
    a:array[0..100,0..100]of longint;
    f:array[0..100]of boolean;
    n,t,i,j,l:longint;
    flag:boolean;

function min(x:longint):longint;
var i,j,l:longint;
begin
  min:=0;
  l:=0;
  for i:=x+1 to n do
    begin
      inc(l);
      c[l]:=a[n,i];
    end;
  for i:=1 to n-x do
    for j:=i+1 to n-x do
      if c[i]>c[j] then
        begin
          c[0]:=c[i];
          c[i]:=c[j];
          c[j]:=c[0];
        end;
  l:=1;
  for i:=n downto 1 do
    if f[i]=true then
      begin
        min:=min+c[l]*i;
        inc(l);
      end;
end;

function max(x:longint):longint;
var  i,j,l:longint;
begin
  max:=0;
  for i:=x+1 to n do
    begin
      inc(l);
      c[l]:=a[n,i];
    end;
  for i:=1 to n-x do
    for j:=i+1 to n-x do
      if c[i]>c[j] then
        begin
          c[0]:=c[i];
          c[i]:=c[j];
          c[j]:=c[0];
        end;
  l:=1;
  for i:=1 to n do
    if f[i]=true then
      begin
        max:=max+c[l]*i;
        inc(l);
      end;
end;

procedure dfs(x,y:longint);
var  i:longint;
begin
  if flag then exit;
  if x>n then
    begin
      if (y=t) then
        begin
          for i:=1 to n do write(b[i],' ');
          writeln;
          flag:=true;
        end;
      exit;
    end;
  for i:=1 to n do
    if f[i]=true then
      begin
        if x>n div 2 then
          if b[n-x+1]>i  then continue;
        if y+i*a[n,x]>t then continue;
        f[i]:=false;
        if (t-y-i*a[n,x]>=min(x))and(t-y-i*a[n,x]<=max(x)) then
          begin
            b[x]:=i;
            dfs(x+1,y+i*a[n,x]);
            f[i]:=true;
            b[x]:=0;
          end
        else f[i]:=true;
      end;
  if l=1 then exit;
end;

begin
  assign(input,'easy.in');
  assign(output,'easy.out');
  reset(input);
  rewrite(output);
  repeat
    flag:=false;
    fillchar(a,sizeof(a),#0);
    read(n,t);
    if (n=0)and(t=0) then break;
    a[1,1]:=1;
    for i:=2 to n do
      for j:=1 to i do
         a[i,j]:=a[i-1,j-1]+a[i-1,j];
    fillchar(f,sizeof(f),true);
    fillchar(b,sizeof(b),#0);
    dfs(1,0);
  until (n=0)and(t=0);
  close(input);
  close(output);
end.

NO.3

Description

奶牛们喜欢在黑暗的环境里睡觉。当她们每晚回到牛棚准备睡觉时,牛棚里有L(3<=L<=50)盏灯仍然亮着。所有灯的开关按编号升序排成一列,最左边的那个开关控制1号灯(所谓控制,也就是如果1号灯现在亮着,那么按这个开关会使1号灯熄灭,否则这个操作会使1号灯被点亮)。由于奶牛们的蹄子过于粗大,没法方便地按开关,她们总是用一个特制的干草叉来进行对开关的操作。这个叉子设计了T(1<=T<=7)个叉尖,相邻叉尖的距离正好与相邻开关的距离相等。但是现在有些叉尖被折断了。比如说,T=4的一个干草叉,它的第3根叉尖被折断了,我们就用’1101’来描述它。
如果把这个叉子的最左端对准那一列开关的最左端,按下,那1号、2号和4号灯的状态会被改变(3号灯的状态不变,因为那个叉尖被折断了)。在进行这样的操作的时候,任何一个叉尖都必须有一个对应的开关,也就是说,叉子的边缘不能在那一列开关的范围外,即使边缘处的叉尖已经被折断。
现在,你已经知道了各个灯的状态,以及干草叉现在的情况,请你找出一个操作序列,使得在所有操作完成之后,仍然亮着的灯的数目最少。

Input

第1行: 两个用空格隔开的整数:L 和 T
第2行: 一个长度为L的字符串,串中不含空格且元素均为’0’或’1’。第i个元素是’1’则表示第i盏灯亮着,是’0’的话就表示第i盏灯已经被关掉
第3行: 一个长度为T的字符串,只含’0’或’1’(同样不含空格)。如果第i个元素是’1’,说明干草叉的第i根叉尖仍完好无损,否则说明第i根叉尖已经被折断

Output

第1行: 输出一个正整数K,即为了达到目的一共需要用叉子按多少次开关

Sample Input

10 4
1111111111
1101

Sample Output

5

Hint

【样例说明】
所有的10盏灯都开着。奶牛们使用的干草叉有4个齿,其中第3个齿已经被折断了。
1111111111 开始
1100101111 操作 3(即为把叉子的第一个齿对准第3个开关,按下)
0001101111 操作 1
0000000111 操作 4
0000001010 操作 7
0000010000 操作 6
最后,有1盏灯仍然亮着。这是借助这个干草叉所能得到的最佳结果。当然,可行操作还有很多(最后剩下的灯可能不同)。


思路:状压DP
设f[i][j][k]为这一段的结尾在i,状态为j,有k盏灯亮着的最小的方案数
状态就戳和不戳。
最后扫一遍找最小值


代码:

uses math;
var n,m,light,cc,kn,mx,w,i,j,k,x1,x2,z:longint;
    x:array[0..52]of longint;
    f:array[0..51,0..128,0..51]of longint;
    bool:boolean;
    cr:char;

procedure main;
begin
        fillchar(f,sizeof(f),$7f);
        mx:=f[4,1,1];
        f[m,light,kn]:=0;
        w:=1 shl (m-1);
        for i:=m to n do
                for j:=0 to 1 shl m do
                        for k:=0 to i do
                                if f[i,j,k]<>mx then
                                begin
                                        x1:=j;
                                        if (j and w)<>0 then x1:=x1-w;
                                        f[i+1,x1*2+x[i+1],k+x[i+1]]:=min(f[i+1,x1*2+x[i+1],k+x[i+1]],f[i,j,k]);
                                        x1:=j xor cc;
                                        x2:=k;
                                        for z:=1 to m do
                                        if ((x1 and (1 shl(z-1))>0)and(j and (1 shl (z-1))=0)) then inc(x2)
                                        else if ((x1 and (1 shl(z-1))=0)and(j and (1 shl(z-1))>0)) then dec(x2);
                                        if (x1 and w)<>0 then x1:=x1-w;
                                        f[i+1,x1*2+x[i+1],x2+x[i+1]]:=min(f[i+1,x1*2+x[i+1],x2+x[i+1]],f[i,j,k]+1);
                                end;
        bool:=true;
        for k:=0 to n do
        begin
                if not bool then break;
                for j:=0 to 1 shl m do
                        if f[n+1,j,k]then
                        begin
                                mx:=f[n+1,j,k];
                                bool:=false;
                        end;
        end;
        writeln(mx);
        close(input);
        close(output);
end;

procedure init;
begin
        assign(input,'xlite.in');
        assign(output,'xlite.out');
        reset(input);
        rewrite(output);
        readln(n,m);
        for i:=1 to n do
        begin
                read(cr);
                if cr='1' then x[i]:=1;
        end;
        readln;
        for i:=1 to m do
        begin
                read(cr);
                if cr='1' then cc:=cc+1 shl (m-i);
                light:=light+1 shl (m-i)*x[i];
                if x[i]=1 then inc(kn);
        end;
end;

begin
    init;
    main;
end.

NO.4 巴比伦Gate of Babylon

JZOJ 8.12 B组总结_第1张图片
JZOJ 8.12 B组总结_第2张图片
JZOJ 8.12 B组总结_第3张图片


思路:组合数+逆元+DFS
先想几个问题,如果有m个苹果放进n个篮子里,篮子可以为空,苹果要放完,方案数:C(n-1,m+n-1)
如果有m个苹果放进n个篮子,篮子可以为空,苹果也不一定要放完,方案数:C(n-1,n+m-1)+C(n-1,n+m-2)+…+C(0,n-1)=C(m,n+m-1)+C(m-1,n+m-2)+…+C(0,n-1)=C(n,n+m)
想到这里,就可以知道所有的宝物都没有限制的情况下的方案数
那么就会想到容斥原理,ans=原方案数-一个物品超标+两个超标-三个超标…
因为T<=15,所以递归暴力枚举。
怎样保证一个物品一定超标?
假设此物有a[x]个,至少选a[x]+1个才保证超标。
所以暴力枚举一个物品是否超标,把a[x]+1加进sum里,最后超标的方案数为
C(m-sum,n+m-sum)(判断正负)
解释:这就是已经选了sum个物品,剩下m-sum个物品,放进n个篮子里且不一定放完的方案数。
但是会超时。所以C要快速求,C(n,m)%p=C(n%p,m%p)*C(n/p,m/p)%p。
同时算好阶乘前缀和,还要用快速幂。


代码:

#include
#include
using namespace std;
int n,m,p,t,b[20];
long long x[100005],jc[100005],ans=0;
long long ksm(long long x,long long y)
{
    long long r=x,ans=1;
    while (y)
    {
        if (y&1) ans=(ans*r)% p;
        r=r*r%p;
        y>>=1;
    }
    return ans;
}
long long C(long long l,long long r)
{
    if (lreturn 0;
    if (lreturn jc[l]*x[r]%p*x[l-r]%p;
    return C(l%p,r%p)*C(l/p,r/p)%p;

}
void dfs(int l,int r,int s)
{
    if (l==t+1) 
    {
        ans=(ans+r*C(m+n-s,m-s))%p;
        return;
    }
    dfs(l+1,-r,s+b[l]+1);
    dfs(l+1,r,s);
}
int main()
{
    freopen("babylon.in","r",stdin);
    freopen("babylon.out","w",stdout);
    scanf("%d%d%d%d",&n,&t,&m,&p);
    for (int i=1;i<=t;i++) scanf("%d",&b[i]);
    jc[0]=1;
    for (int i=1;i<=p;i++) jc[i]=(jc[i-1]*i)%p;
    x[p-1]=ksm(p-1,p-2);
    x[0]=1;
    for (int i=p-2;i;i--) x[i]=(x[i+1]*(i+1))% p;
    dfs(1,1,0);
    printf("%lld\n",(ans%p+p)%p);
    return 0;
}

转载于:https://www.cnblogs.com/Comfortable/p/8412256.html

你可能感兴趣的:(JZOJ 8.12 B组总结)