Problem A Range Product
简要题意:给定 (a,b) 两个数字,输出 (prod_{i=a}^b i) 是正数,负数还是零。 (-10^9 le ale ble 10^9)
tag:分类讨论
题解:分类讨论一下没了
#include <cstdio>
int main (){
int a,b;scanf ("%d%d",&a,&b);
if (a<=0&&b>=0) puts("Zero");
else if (a<0&&b<0&&((b-a+1)&1)) puts("Negative");
else puts("Positive");
return 0;
}
Problem B Box and Ball
简要题意:(N) 个盒子,第一个盒子里是个红球,其他 (N-1) 个盒子里都是白球。现在给出 (M) 次操作,操作的方式是在一个盒子中随机取出一个球放入另一个盒子中。在 (M) 次操作完后,求出红球可能在的盒子的总数。(N,M le 10^5)
tag:枚举,按题意模拟
题解:按题意模拟,记录每个盒子是否可能有球
#include <cstdio>
const int N=100005;
bool vis[N];int cnt[N];
int main (){
int n,m;scanf ("%d%d",&n,&m);vis[1]=true;
for (int i=1;i<=n;i++) cnt[i]=1;
for (int i=1,x,y;i<=m;i++){
scanf ("%d%d",&x,&y);
vis[y]|=vis[x],cnt[x]--,cnt[y]++;
if (cnt[x]==0) vis[x]=false;
}
int ans=0;for (int i=1;i<=n;i++) ans+=vis[i];
printf ("%d",ans);
return 0;
}
Problem C Knot Puzzle
简要题意:有 (N) 条绳子,第 (i) 条的长度是 (A_i) 。 相邻两条绳子打了结,每次选一段绳子, 如果这段绳子的总长度大于给定的变量 (L) ,我们可以在这段绳子中选一个解开。问是否有一种方案把所有绳子都解开。 (N le 10^5)
tag:贪心
题解:考虑进行到只剩一个绳结,则两段绳子的长度和必须大于等于L,否则这个绳结没法解。所以找到整根绳子中长度和大于等于L的两段,将左边的绳结从左往右解开,右边的绳结从右往左解开,最后将这个绳结解开。
#include <cstdio>
const int N=100005;
int a[N];
int main (){
int n,L;scanf ("%d%d",&n,&L);
for (int i=1;i<=n;i++) scanf ("%d",&a[i]);
for (int i=1;i<n;i++){
if (a[i]+a[i+1]>=L) {
puts("Possible");
for (int j=1;j<i;j++) printf ("%d
",j);
for (int j=n-1;j>i;j--) printf ("%d
",j);
printf ("%d",i);
return 0;
}
}
puts("Impossible");
return 0;
}
Problem D Stamp Rally
简要题意:一张连通图, (q) 次询问从两个点 (x) 和 (y) 出发,希望经过的点(不重复)数量等于 (z) ,经过的边最大编号最小是多少。(n,q le 10^5)
tag:整体二分,带撤销并查集
题解:(Atcoder) 里罕见的数据结构题,对所有询问一起二分答案,用并查集判断是否可行,挺套路的一种题。
#include <cstdio>
#include <vector>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int N=1e5+5;
int ans[N];
inline int Max(int a,int b){
return a>b?a:b;
}
struct Edge{
int u,v;
}e[N];
struct Node{int id,x,y,z;};
int fa[N];
inline int getrt(int x){while (fa[x]!=x)x=fa[x];return x;}
int size[N],deep[N],s[N][2],top=0;
bool tag[N];
inline void link(int x,int y){
if (deep[x]<deep[y]) swap(x,y);
++top,s[top][0]=x,s[top][1]=y;
fa[y]=x,size[x]+=size[y];
if (deep[x]==deep[y]) deep[x]++,tag[top]=1;else tag[top]=0;
}
inline void pop(){
fa[s[top][1]]=s[top][1],size[s[top][0]]-=size[s[top][1]];
deep[s[top][0]]-=tag[top];tag[top]=0;--top;
}
inline void work(int l,int r,vector <Node > now){
if (now.size()==0) return;
if (l==r){
for (int i=0;i<now.size();i++) ans[now[i].id]=l;
return;
}
vector <Node > mx,mn;mx.clear(),mn.clear();
int mid=(l+r)>>1,nowtop=top;
for (int i=l,x,y;i<=mid;i++) if ((x=getrt(e[i].u))!=(y=getrt(e[i].v))) link(x,y);
for (int i=0;i<now.size();i++){
int x=getrt(now[i].x),y=getrt(now[i].y),sum=0;
if (x!=y) sum=size[x]+size[y];
else sum=size[x];
if (sum>=now[i].z) mn.push_back(now[i]);
else mx.push_back(now[i]);
}
work(mid+1,r,mx);
while (top>nowtop) pop();
work(l,mid,mn);
}
int main (){
int n,m,q;scanf ("%d%d",&n,&m);
for (int i=1;i<=m;i++)
scanf ("%d%d",&e[i].u,&e[i].v);
scanf ("%d",&q);
vector <Node > t;t.clear();
for (int i=1;i<=q;i++){
int x,y,z;scanf ("%d%d%d",&x,&y,&z);
t.push_back((Node){i,x,y,z});
}
for (int i=1;i<=n;i++) fa[i]=i,size[i]=deep[i]=1;
work(1,m,t);
for (int i=1;i<=q;i++) printf ("%d
",ans[i]);
return 0;
}
Problem E Candy Piles
简要题意:桌子上有 (n) 堆糖,第 (i) 堆有 (a_i) 个,两个人轮流玩游戏,有两种操作:
- 把最多的一堆吃完
- 把每一堆吃掉一个
吃完的人输,问两人足够聪明的博弈结果。 (nle 10^5,a_ile 10^9)
tag:博弈,推结论
题解:首先转化为平面上,按照 (a_i) 从大到小排序,两个人轮流走,可以向上或者向右走,走不了的人失败
有个结论,((x,y)) 这个状态如果对于先手是必胜态,那么 ((x+1,y+1)) 也对于先手是必胜态,反之亦然
所以我们可以直接找到 ((0,0)) 对应的是哪个点 ((i,i)) ,算出这个点到上方和右方轮廓的距离,只要有一个是偶数,就先手必胜。
#include <cstdio>
#include <algorithm>
using namespace std;
const int N=100005;
int a[N];
bool cmp(int a,int b){return a>b;}
int main (){
int n;scanf ("%d",&n);
for (int i=1;i<=n;i++) scanf ("%d",&a[i]);
sort(a+1,a+n+1,cmp);
for (int i=1;i<=n;i++)
if (i+1>a[i+1]){
int j=i+1;while (a[j]==i) j++;
if (((j-i-1)&1)|((a[i]-i)&1)) puts("First");
else puts("Second");
return 0;
}
return 0;
}
Problem F Leftmost Ball
简要题意:给你 (n) 种颜色的球,每个球有 (k) 个,把这 (n imes k) 个球排成一排,把每一种颜色的最左边出现的球涂成白色(初始球不包含白色),求有多少种不同的颜色序列,答案对 (10^9+7) 取模 。(nle 2000)
tag:计数类 (dp) ,组合数学
题解:设 (f[i][j]) 表示填了 (i) 个白色球,(j) 种彩色球的方案数,那么显然 (jle i) 。
考虑这个转移,首先可以填一个白色,就是 (f[i][j]+=f[i-1][j]) 。
放新的颜色的球:即从 (f[i][j-1]) 转移,先在剩下 (n-j+1) 种没有放置过的颜色中选择一个作为这次放置的颜色,放置在第一个空位(按转移规则,已经放置的 (i) 个白球一定都在当前第一个空位的前面,因此必定合法)。放完后还剩 (k-2) 的这个颜色的球没有放,显然只需要在剩下的后面 (n imes k-i-(j-1) imes (k-1)-1) 个空位里找任意 (k-2) 个空位放就好,有 $ inom{n imes k−(i+(j−1) imes (k−1))}{k−2}$ 种方案
所以总的转移方程是
注意特判 (k=1) 的情况
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int N=2005,Mod=1e9+7;
int dp[N][N],fac[N*N],inv[N*N];
inline int C(int n,int m){
return 1ll*fac[n]*inv[m]%Mod*inv[n-m]%Mod;
}
inline int qpow(int a,int b){
int ans=1;
while (b){
if (b&1) ans=1ll*ans*a%Mod;
a=1ll*a*a%Mod,b>>=1;
}
return ans;
}
int main (){
int n,k;scanf ("%d%d",&n,&k);
if (k==1) {puts("1");return 0;}
dp[0][0]=1;fac[0]=1;
for (int i=1;i<=n*k;i++) fac[i]=1ll*fac[i-1]*i%Mod;
inv[n*k]=qpow(fac[n*k],Mod-2);for (int i=n*k-1;i>=0;i--) inv[i]=1ll*inv[i+1]*(i+1)%Mod;
for (int i=0;i<=n;i++) dp[i][0]=1;
for (int i=1;i<=n;i++)
for (int j=0;j<=i;j++)
dp[i][j]=dp[i-1][j]+1ll*dp[i][j-1]*(n-j+1)%Mod*C(n*k-(i+(j-1)*(k-1))-1,k-2)%Mod,dp[i][j]%=Mod;
printf ("%d",dp[n][n]);
return 0;
}