[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[i−1,j]*(1−pi−1)^j+f[i−1,j+1]*(1−(1−pi−1)^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; }
【推荐】还在用 ECharts 开发大屏?试试这款永久免费的开源 BI 工具!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步