[3.17校内训练赛]

hzwer出的bzoj训练赛。

A.[bzoj1823][jsoi2010]满汉全席

有n种食材,每道食材可以做出两道菜。然后有m为评审,每位评审只对两道菜满意。你有恰好这n种食材各一个,你要决定每种食材做哪种菜,判断是否有一种做法满足所有评审。n<=100,m<=1000

题解:2-sat裸题,对于每个评审满意的两道菜i,j,从i'向j,从j'向i连边,表示选了i的另一道菜必须选第j道菜,另一个同理。

然后按照2-sat那么搞呗。

#include<iostream>
#include<cstdio>
#define MAXN 200
#define INF 2000000000
#include<queue> 
#include<cstring>
using namespace std;
inline int read()
{
    int  x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-') f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0'; ch=getchar();}
    return x*f;
}

bool inq[MAXN+5];
int head[MAXN+5],cnt,n,m,q[MAXN+5],cft[MAXN+5],top,dn;
int dfn[MAXN+5],low[MAXN+5],cc,belong[MAXN+5],in[MAXN+5],col[MAXN+5];
struct edge{
    int to,next;
}e[1000000];
char st[1000];

int get()
{
    scanf("%s",st+1);int x=0;
    for(int i=2;st[i];i++)
        x=x*10+st[i]-'0';
    if(st[1]=='m')x+=n;
    return x;
}

void ins(int f,int t)
{
    e[++cnt].next=head[f];head[f]=cnt;
    e[cnt].to=t;
}

void tarjan(int x)
{
    q[++top]=x;dfn[x]=low[x]=++dn;inq[x]=1;
    for(int i=head[x];i;i=e[i].next)
    if(!dfn[e[i].to]){tarjan(e[i].to);low[x]=min(low[x],low[e[i].to]);}
    else if(inq[e[i].to])low[x]=min(low[x],dfn[e[i].to]);
    if(dfn[x]==low[x])
    for(++cc;q[top+1]!=x;inq[q[top]]=0,belong[q[top--]]=cc);
}

int main()
{
    int t=read();
    while(t--)
    {
        cc=n=read();m=read();cnt=top=0;memset(head,0,sizeof(head));dn=0;
        memset(belong,0,sizeof(belong));memset(dfn,0,sizeof(dfn));memset(low,0,sizeof(low));
        for(int i=1;i<=n;i++)cft[i]=i+n,cft[i+n]=i;
        for(int i=1;i<=m;i++)
        {
            int x=get(),y=get();
            ins(cft[x],y);ins(cft[y],x);    
        }
        for(int i=1;i<=n<<1;i++)if(!dfn[i])tarjan(i);
        int i;
        for(i=1;i<=n;i++)if(belong[i]==belong[i+n]){puts("BAD");break;}
        if(i>n)puts("GOOD");
    }
    return 0;
}

B.[bzoj1822][jsoi2010]Frozen Nova冷冻波

有n个巫师,每个巫师有坐标,攻击范围和冷却时间。有q棵树,每棵树抽象成圆,给定坐标和半径。还有m个小精灵,给定坐标。

对于一个巫师和一个小精灵,如果小精灵在巫师的攻击距离内而且它们的连线不与任何的树相交,那么巫师可以在一次攻击中消灭这个小精灵。

一个巫师在攻击后必须等待冷却时间结束才能发动下一次攻击。求最少过了多久可以消灭所有小精灵,或者根本无法杀掉所有小精灵?

n,m,k<=200

题解:很容易想到二分答案+网络流check,所以剩下的就是计算几何啦。

作为一名计算几何菜鸡,我一下午就wa了个几十次吧,最后发现是sb错误。真的难受。

#include<iostream>
#include<cstdio>
#define INF 2000000000
#include<queue> 
#include<cmath>
#include<cstring>
#define S 0
#define T 401
using namespace std;
inline int read()
{
    int  x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-') f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0'; ch=getchar();}
    return x*f;
}
  
bool mp[T+5][T+5];
int head[T+5],n,m,cnt=1,k,q[T+5];
struct edge{
    int to,next,w;
}e[1000000];
inline double sqr(double x){return x*x;}
struct POINT{double x,y,r,t;
    POINT operator-(POINT a){return (POINT){a.x-x,a.y-y};}
    double operator*(POINT a){return x*a.y-y*a.x;}
}s[T],t[T],w[T];
double dis(POINT x,POINT y){return sqrt(sqr(x.x-y.x)+sqr(x.y-y.y));}
double dot(POINT x,POINT y){return x.x*y.x+x.y*y.y;}
 
queue<int> qu;
bool used[T+5];
  
void ins(int f,int t,int w)
{
    e[++cnt].next=head[f];head[f]=cnt;
    e[cnt].to=t;e[cnt].w=w;
}
  
void insw(int f,int t,int w){ins(f,t,w);ins(t,f,0);}
  
int dfs(int x,int f)
{
    if(x==T)return f;
    int use=0;
    for(int i=head[x];i;i=e[i].next)
    if(e[i].w&&q[e[i].to]==q[x]+1)
    {
        int w=dfs(e[i].to,min(f-use,e[i].w));
        use+=w;e[i].w-=w;e[i^1].w+=w;
        if(use==f)return f;
    }
    return use;
}
  
bool bfs()
{
    memset(q,0,sizeof(q));q[S]=1;qu.push(S);
    while(!qu.empty())
    {
        int u=qu.front();qu.pop();
        for(int i=head[u];i;i=e[i].next)    
        if(e[i].w&&!q[e[i].to])
        {
            q[e[i].to]=q[u]+1;
            qu.push(e[i].to);   
        }
    }
    return q[T]>0;
}
  
bool check(POINT x,POINT y,POINT z)
{
    double d=fabs((z-x)*(y-x)),len=dis(x,y);
    if((double)d/(double)len>z.r) return true;
    if(dot(y-x,z-x)<0)return dis(x,z)>z.r;
    else if(dot((x-y),(z-y))<0)return dis(y,z)>z.r;
    return false;
}
  
void build(int x)
{
    cnt=1;memset(head,0,sizeof(head));
    for(int i=1;i<=n;i++)insw(S,i,x/w[i].t+1);
    for(int j=1;j<=m;j++)insw(j+n,T,1);
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
            if(mp[i][j])
            insw(i,j+n,1);
}
  
int solve()
{
    int l,r,mid,ans=-1,i,j,sum;l=0,r=200000000;
    while(l<=r)
    {
        mid=l+r>>1;sum=0;build(mid);
        while(bfs())sum+=dfs(S,INF);
        //cout<<mid<<" "<<sum<<"GGF"<<endl;
        if(sum==m)ans=mid,r=mid-1;
        else l=mid+1;
    }
    return ans;
}
  
int main()
{
    n=read();m=read();k=read();
    for(int i=1;i<=n;i++)
    {w[i].x=read();w[i].y=read();w[i].r=read();w[i].t=read();}
    for(int i=1;i<=m;i++)
    {s[i].x=read();s[i].y=read();}
    for(int i=1;i<=k;i++)
    {t[i].x=read();t[i].y=read();t[i].r=read();}
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
        if(dis(w[i],s[j])<=w[i].r)
        {
            bool yes=true;
            for(int l=1;l<=k;l++)
            {
                if(!check(w[i],s[j],t[l])){yes=false;break;}
            }
            if(yes)mp[i][j]=1; 
        }
    cout<<solve();
    return 0;
}

C.[bzoj4008][hnoi2015]亚瑟王

给定n张卡片,每张卡片有一个发动概率pi和伤害d。你进行r轮游戏,每次都从第一张卡片开始,每次对于一张没发动过的卡片,你都有pi的几率发动它并结束这一轮。当然,你也可能什么都不发动。求最后伤害的期望值。

我根本不会,求助ditoly大佬。

ditoly大佬的题解:我们可以把问题抽象为一开始我有r条线位于点1,我可以选择删掉其中的任何一条线,使得其他线都往后走一格(就是这一轮发动它并停止了),或者不删掉,直接走,两者的概率不同。

先用r[i][j]表示有i条线走到了j点,选的可能性。则r[i][j]可以是之前已经选了,或者恰好选最后这个。r[i][j]=r[i-1][j]+(1-r[i-1][j])*p[i]

再用r2[i][j]表示从起始状态走到这个状态的可能性,则r2[i][j]可以是从r2[i+1][j-1]转移过来的,这时选了第i个,概率是r[i+1][j];或者也可以从r2[i][j-1]转移过来,相应的概率是(1-r[i][j])

最后计算期望。相似的,f[i][j]可以从f[i+1][j-1]和f[i][j-1]转移,f[i][j]=(f[i+1][j-1]+r2[i+1][j-1]*s[j])*r[i+1][j]+f[i][j-1]*r[i][j]。所以这道题就解完了。

然后再说一下网上的比较简单的题解:用f[i][j]表示第i个人得到第j个机会的概率,那么这时候f[i,j]=f[i1,j]*(1pi1)^j+f[i1,j+1]*(1(1pi1)^j+1)

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
inline int read()
{
    int  x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-') f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0'; ch=getchar();}
    return x*f;
}
 
double q[225];
double s[225];
double f[135][225],r[135][225],r2[135][225];
int n,m;
double ans=0;
 
int main()
{ 
    int T=read();
    while(T--)
    {
        n=read();m=read();memset(f,0,sizeof(f));ans=0;
        memset(r,0,sizeof(r));memset(r2,0,sizeof(r2));
        for(int i=1;i<=n;i++)scanf("%lf",&q[i]),s[i]=read();
        for(int i=1;i<=n;i++)
            for(int j=1;j<=m;j++)
                r[j][i]=r[j-1][i]+(1-r[j-1][i])*q[i];
        r2[m][0]=1;
        for(int i=m;i>=0;i--)
            for(int j=1;j<=n;j++)
                r2[i][j]=r2[i+1][j-1]*r[i+1][j]+r2[i][j-1]*(1-r[i][j]);
        for(int i=m;i>=0;i--)
            for(int j=1;j<=n;j++)
                f[i][j]=f[i+1][j-1]*r[i+1][j]+s[j]*r2[i+1][j-1]*r[i+1][j]+f[i][j-1]*(1-r[i][j]);
        for(int i=0;i<=m;i++)ans+=f[i][n];
        printf("%0.10lf\n",ans);
    }    
    return 0;
}

 

 

D.[bzoj3144][hnoi2013]切糕

有n*m个格子,每个格子可以填1-h中的任意数,每个格子填每个数都有不同的不满意度。你要在相邻的两个格子填的数字之差不超过d的情况下,求出最小的不满意度。n,m,h<=40

题解:最小割。

对于每个格子,拆h个点,(i,j,k-1)向(i,j,k)连边,流量为Sijk,每个(i,j,h)向T连费用为INF的边。

然后对于每个点(i,j,k)且k>d,从它向四周的点的(k-d)号点连INF的边,使其无法被割。

然后这样可以保证正确性。如果割掉了连接(i,j,k)到(i,j,k+1)的边,那么它就会顺着边流到(i',j',k-d),之后在那一竖又回流回到(i,j)这一竖。

复杂度O(能过)

#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#define S 0
#define T 64001
#define INF 2000000000
using namespace std;
int read()
{
    int x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-') f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0'; ch=getchar();}
    return x*f;
}

int qu[T+5],top,tail;
int n,m,h,d,cnt=1;
int head[T+5],q[T+5],cur[T+5];
const int dis[4][2]={{1,0},{-1,0},{0,1},{0,-1}};
struct edge{
    int to,next,w;
}e[T*100];

inline int num(int x,int y,int z){return z==0?S:(z-1)*n*m+(x-1)*m+y;}
inline void ins(int f,int t,int w){e[++cnt].next=head[f];head[f]=cnt;e[cnt].to=t;e[cnt].w=w;}
inline void insw(int f,int t,int w){ins(f,t,w);ins(t,f,0);}

int dfs(int x,int f)
{
    if(x==T)return f;
    int used=0;
    for(int i=cur[x];i;i=e[i].next)
    if(e[i].w&&q[e[i].to]==q[x]+1)
    {
        int w=dfs(e[i].to,min(e[i].w,f-used));
        used+=w;e[i].w-=w;e[i^1].w+=w;
        if(e[i].w)cur[x]=i;
        if(used==f)return used;
    }
    if(!used)q[x]=-1;
    return used;
}

bool bfs()
{
    memset(q,0,sizeof(q));q[S]=1;qu[top=1]=S;tail=0;
    while(top!=tail)
    {
        int u=qu[++tail];
        for(int i=head[u];i;i=e[i].next)
        if(e[i].w&&!q[e[i].to])
        {
            q[e[i].to]=q[u]+1;
            qu[++top]=e[i].to;
        }
    }
    return q[T]>0;
}

int main()
{
    n=read();m=read();h=read();d=read();
    for(int k=1;k<=h;k++)
        for(int i=1;i<=n;i++)
            for(int j=1;j<=m;j++)
            {int x=read();insw(num(i,j,k-1),num(i,j,k),x);}
    for(int i=1;i<=n;i++)for(int j=1;j<=m;j++)insw(num(i,j,h),T,INF);
    for(int i=1;i<=n;i++)for(int j=1;j<=m;j++)for(int k=d+1;k<=h;k++)
    for(int l=0;l<4;l++)
    {
        int xx=i+dis[l][0],yy=j+dis[l][1];
        if(xx<1||yy<1||xx>n||yy>m)continue;
        insw(num(i,j,k),num(xx,yy,k-d),INF);
    }
    int ans=0;
    while(bfs())
    {for(int i=S;i<=T;i++)cur[i]=head[i];ans+=dfs(S,INF);}
    printf("%d\n",ans);
    return 0;
}

E.[bzoj3573][hnoi2014]米特运输

题意:给定一棵树,每个点有权值,你要修改尽量少的点使得:1)每个点的权值=它的子节点的权值和2)它的子节点的权值相同 n<=500000

题解:确定一个值以后,整棵树都确定了。我们设根是x,则每个点都可以表示为x的若干分之一。我们只要把每个点的权值*这个若干就能得到对应的x,求出现次数最多的x就可以了。但是x可能非常大,所以我们把它取余一下,然后扔到map里就可以啦

复杂度nlogn

#include<iostream>
#include<cstdio>
#include<queue> 
#include<cstring>
#include<map>
#define MAXN 500000
#define mod 998244353
#define ll long long
using namespace std;
inline int read()
{
    int  x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-') f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0'; ch=getchar();}
    return x*f;
}
 
struct edge{
    int to,next;
}e[2*MAXN+5];
int n,s[MAXN+5],num[MAXN+5],head[MAXN+5],f[MAXN+5],cnt=0;
map<ll,int> mp;
 
void ins(int f,int t){e[++cnt].next=head[f];head[f]=cnt;e[cnt].to=t;}
 
void dfs(int x,int fa)
{
    f[x]=fa;
    for(int i=head[x];i;i=e[i].next)
    if(e[i].to!=fa)dfs(e[i].to,x),num[x]++;
}
 
void solve(int x,ll nn,int fa)
{
    mp[(1LL*nn*s[x])%mod]++;
    for(int i=head[x];i;i=e[i].next)if(e[i].to!=fa)
    solve(e[i].to,nn*num[x]%mod,x);
}
 
int main()
{
    n=read();
    for(int i=1;i<=n;i++)s[i]=read();
    for(int i=1;i<n;i++)
    {int u=read(),v=read();ins(u,v);ins(v,u);}dfs(1,0);
    solve(1,1,0);int ans=0;
    for(map<ll,int>::iterator it=mp.begin();it!=mp.end();++it)
        ans=max(ans,it->second);
    cout<<n-ans;
    return 0;
}

 

转载于:https://www.cnblogs.com/FallDream/p/hzwer317.html

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值