[JSOI2015]salesman
树形 (dp)。一个点可以走 (x) 次,相当于最多可以进入 (x-1) 棵子树,每一次挑收益最大且大于 (0) 的走就可以取到最大值。至于判断多解,只需要看有没有两颗子树的收益相同或者有收益为 (0),即可走也不走的子树。如果从子树走有多解,那么从根走也一定有多解。可以额外开一个数组记录是否存在多解,(dp) 时直接维护。
#include <cstdio>
#include <algorithm>
#define inf (2100000000)
typedef long long ll;
inline int rd(){
int x=0,p=1;
char a=getchar();
while((a<48||a>57)&&a!='-')a=getchar();
if(a=='-')p=-p,a=getchar();
while(a>47&&a<58)x=(x<<1)+(x<<3)+(a&15),a=getchar();
return x*p;
}
inline int min(int x,int y){return x<y?x:y;}
const int N=100002;
struct Edge{
int to,next;
}edge[N<<1];
int head[N],cnt;
int n;
int a[N],b[N];
int f[N],vis[N],st[N];
bool comp(const int &x,const int &y){
return f[x]>f[y];
}
inline void add(int f,int t){
edge[++cnt].next=head[f];
edge[cnt].to=t;
head[f]=cnt;
}
inline void dp(int u,int ft){
f[u]=a[u];
for(int i=head[u];i;i=edge[i].next){
int v=edge[i].to;
if(v==ft)continue;
dp(v,u);
}
int hd=0,top=0;
for(int i=head[u];i;i=edge[i].next){
int v=edge[i].to;
if(v==ft)continue;
st[++top]=v;
}
std::sort(st+1,st+top+1,comp);
while(hd<min(top,b[u]-1)&&f[st[hd+1]]>=0)
f[u]+=f[st[++hd]],vis[u]|=vis[st[hd]];
if(!hd)return;
if((hd<top&&f[st[hd]]==f[st[hd+1]])||f[st[hd]]==0)vis[u]=1;
}
int main(){
n=rd(),b[1]=inf;
for(int i=2;i<=n;i++)a[i]=rd();
for(int i=2;i<=n;i++)b[i]=rd();
for(int i=1;i<n;i++){
int u=rd(),v=rd();
add(u,v),add(v,u);
}
dp(1,0);
printf("%d
",f[1]);
if(vis[1])puts("solution is not unique");
else puts("solution is unique");
return 0;
}
[JSOI2015]子集选取
结论题,答案为 (2^{nk})。
大致推导:由于集合内的元素不影响结果,可以先算出 (n=1) 时的结果 (ans),最后答案为 (ans^n)。此时只有一种元素,所以如果一个子集里没有这种元素,则包含这个元素的集合只可能存在于它的右上方。这相当于从三角形左下向上或向右走 (k) 步,这样走出来一条线,上方的子集包含这个元素,下方的没有包含。总方案数是 (2^k),有 (n) 种元素答案就是 (2^{nk})。
#include <cstdio>
typedef long long ll;
inline ll rd(){
ll x=0,p=1;
char a=getchar();
while((a<48||a>57)&&a!='-')a=getchar();
if(a=='-')p=-p,a=getchar();
while(a>47&&a<58)x=(x<<1)+(x<<3)+(a&15),a=getchar();
return x*p;
}
const ll mod=1e9+7;
ll n,k;
inline ll fpow(ll b,ll p){
ll ans=1,tmp=b;
while(p){
if(p&1)ans=ans*tmp%mod;
tmp=tmp*tmp%mod;
p>>=1;
}
return ans;
}
int main(){
n=rd(),k=rd();
printf("%lld
",fpow(2,n*k));
return 0;
}
[JSOI2015]非诚勿扰
第 (x) 个女性选择第 (y) 个男性的概率为:
通过无限等比数列求和公式得到:
按照编号从小到大考虑每一个女性,拿树状数组维护一下前缀和,类似逆序对的求法,具体可参考代码。
(p.s.) 本题需要开 ( ext{long double})。
#include <cstdio>
#include <algorithm>
#define double long double
typedef long long ll;
inline int rd(){
int x=0,p=1;
char a=getchar();
while((a<48||a>57)&&a!='-')a=getchar();
if(a=='-')p=-p,a=getchar();
while(a>47&&a<58)x=(x<<1)+(x<<3)+(a&15),a=getchar();
return x*p;
}
const int N=500002;
int n,m,size[N];
double p,ans;
struct node{
int a,b;
}x[N];
bool comp(const node &x,const node &y){
return (x.a^y.a)?(x.a<y.a):(x.b<y.b);
}
#define lowbit(x) ((x)&(-(x)))
double c[N];
inline void add(int x,double k){
for(;x<=n;x+=lowbit(x))c[x]+=k;
}
inline double query(int x){
double ans=0;
for(;x;x-=lowbit(x))ans+=c[x];
return ans;
}
inline double fpow(double b,int p){
if(!p)return 1.0;
double ans=1.0,tmp=b;
while(p){
if(p&1)ans*=tmp;
tmp*=tmp;
p>>=1;
}
return ans;
}
int main(){
n=rd(),m=rd(),scanf("%Lf",&p);
for(int i=1;i<=m;i++)x[i].a=rd(),x[i].b=rd(),size[x[i].a]++;
std::sort(x+1,x+m+1,comp);
int now=1;
for(int i=1;i<=n;i++){
if(i!=x[now].a)continue;
int cnt=0;
while(i==x[now].a){
cnt++;
double t=p*fpow(1-p,cnt-1)/(1-fpow(1-p,size[i]));
add(x[now].b,t);
ans+=t*(query(n)-query(x[now].b));
now++;
}
}
printf("%.2Lf
",ans);
return 0;
}
[JSOI2015]套娃
考虑贪心。先假设它们全是空的,再按 (b_i) 排序,同时维护 (out_i) 的集合,对于每一个套娃优先选能装下且剩余空间最小的装,容易证明这种方案是最佳的。
#include <cstdio>
#include <algorithm>
#include <set>
typedef long long ll;
inline int rd(){
int x=0,p=1;
char a=getchar();
while((a<48||a>57)&&a!='-')a=getchar();
if(a=='-')p=-p,a=getchar();
while(a>47&&a<58)x=(x<<1)+(x<<3)+(a&15),a=getchar();
return x*p;
}
const int N=200002;
int n;
struct node{
int i,o,b;
}a[N];
bool comp(const node &x,const node &y){
return x.b>y.b;
}
std::multiset<int> s;
ll ans;
int main(){
n=rd();
for(int i=1;i<=n;i++){
a[i].o=rd(),a[i].i=rd(),a[i].b=rd();
s.insert(a[i].o);
ans+=1ll*a[i].i*a[i].b;
}
std::sort(a+1,a+n+1,comp);
for(int i=1;i<=n;i++){
std::multiset<int>::iterator it=s.lower_bound(a[i].i);
if(it==s.begin())continue;
it--;
ans-=1ll*a[i].b*(*it);
s.erase(it);
}
printf("%lld
",ans);
return 0;
}
[JSOI2015]最小表示
对于一个 (DAG) 的一条边 ((u,v)),若删去它可以保持连通性不变,意味着存在另一条路径可以从 (u) 到 (v)。对原图作拓扑排序,用 (n) 个 (bitset) 维护点之间的连通性,按照从一个点到出度为零的点之间的距离从远到近处理它所相连的点以保证删去的边最多,复杂度为 (O(dfrac{n^2}{64(32)})),可以通过本题。
#include <cstdio>
#include <algorithm>
#include <queue>
#include <bitset>
typedef long long ll;
inline int rd(){
int x=0,p=1;
char a=getchar();
while((a<48||a>57)&&a!='-')a=getchar();
if(a=='-')p=-p,a=getchar();
while(a>47&&a<58)x=(x<<1)+(x<<3)+(a&15),a=getchar();
return x*p;
}
const int N=30002,S=100002;
struct Edge{
int to,next;
}edge[S];
int head[N],cnt;
int n,m,in[N],ans;
int x[N],ti,vis[N];
int st[N],top;
std::bitset<N>b[N];
std::queue<int> q;
bool comp(const int &x,const int &y){
return vis[x]<vis[y];
}
inline void add(int f,int t){
edge[++cnt].next=head[f];
edge[cnt].to=t;
head[f]=cnt;
}
int main(){
n=rd(),m=rd();
for(int i=1;i<=m;i++){
int u=rd(),v=rd();
add(u,v),in[v]++;
}
for(int i=1;i<=n;i++)
if(!in[i])q.push(i);
while(!q.empty()){
int u=q.front();q.pop();
x[++ti]=u,vis[u]=ti;
for(int i=head[u];i;i=edge[i].next){
int v=edge[i].to;
in[v]--;
if(!in[v])q.push(v);
}
}
for(int i=n;i;i--){
int u=x[i];
b[u][u]=1,top=0;
for(int i=head[u];i;i=edge[i].next)
st[++top]=edge[i].to;
std::sort(st+1,st+top+1,comp);
for(int i=1;i<=top;i++){
if(b[u][st[i]])ans++;
else b[u]|=b[st[i]];
}
}
printf("%d
",ans);
return 0;
}
[JSOI2015]最大公约数
倍增 + (ST) 表。
有一个结论,是一个长度为 (n) 的序列的所有子序列的最大公约数的个数不会超过 (log n) 个,所以可以枚举左端点 (l),用倍增找到这 (log n) 个公约数对应的区间,区间 (gcd) 可以用 (ST) 表预处理,总复杂度是 (O(nlog^2 n)),可以通过。
#include <cstdio>
typedef long long ll;
inline ll rd(){
ll x=0,p=1;
char a=getchar();
while((a<48||a>57)&&a!='-')a=getchar();
if(a=='-')p=-p,a=getchar();
while(a>47&&a<58)x=(x<<1)+(x<<3)+(a&15),a=getchar();
return x*p;
}
inline ll max(ll x,ll y){return x>y?x:y;}
const int N=100002;
int n,lg[N];
ll a[N],ans;
ll f[N][22];
inline ll gcd(ll a,ll b){
return !b?a:gcd(b,a%b);
}
inline ll query(int l,int r){
int x=lg[r-l+1];
return gcd(f[l][x],f[r-(1<<x)+1][x]);
}
int main(){
n=rd();
for(int i=1;i<=n;i++)a[i]=rd();
lg[0]=-1;
for(int i=1;i<=n;i++)lg[i]=lg[i>>1]+1,f[i][0]=a[i];
for(int j=1;j<=lg[n];j++)
for(int i=1;i+(1<<(j-1))<=n;i++)
f[i][j]=gcd(f[i][j-1],f[i+(1<<(j-1))][j-1]);
for(int l=1;l<=n;l++){
int r=l;
while(r<=n){
ll g=query(l,r);
for(int i=lg[n];~i;i--)
if(r+(1<<i)<=n&&query(l,r+(1<<i))==g)r+=(1<<i);
ans=max(ans,g*(r-l+1));
r++;
}
}
printf("%lld
",ans);
return 0;
}
[JSOI2015]染色问题
组合+容斥。设 (f_{i,j,k}) 为至少 (i) 行没有染色,至少 (j) 列没有染色,至少 (k) 中颜色没有使用的方案数,那么还剩下 ((n-i) imes(m-j)) 个格子,剩下 (c-k) 种颜色,格子也可以不染色,即:
答案即为:
直接枚举 (i,j,k),对于每一对 (i,j) 用 (O(nm)) 的时间复杂度预处理 ((c-k+1)) 的幂,可以做到 (O(nmc))。
#include <cstdio>
typedef long long ll;
inline int rd(){
int x=0,p=1;
char a=getchar();
while((a<48||a>57)&&a!='-')a=getchar();
if(a=='-')p=-p,a=getchar();
while(a>47&&a<58)x=(x<<1)+(x<<3)+(a&15),a=getchar();
return x*p;
}
const int N=402;
const ll mod=1e9+7;
int n,m,c;
ll fac[N],inv[N],p[N*N];
ll ans;
inline ll fpow(ll b,ll p){
ll ans=1,tmp=b;
while(p){
if(p&1)ans=ans*tmp%mod;
tmp=tmp*tmp%mod;
p>>=1;
}
return ans;
}
inline ll C(int n,int m){
return fac[n]*inv[m]%mod*inv[n-m]%mod;
}
int main(){
fac[0]=p[0]=1;
for(int i=1;i<=400;i++)fac[i]=fac[i-1]*i%mod;
inv[400]=fpow(fac[400],mod-2);
for(int i=399;i>=0;i--)inv[i]=inv[i+1]*(i+1)%mod;
n=rd(),m=rd(),c=rd();
for(int k=0;k<=c;k++){
for(int i=1;i<=n*m;i++)p[i]=p[i-1]*(c-k+1)%mod;
for(int i=0;i<=n;i++)
for(int j=0;j<=m;j++){
ll res=C(n,i)*C(m,j)%mod*C(c,k)%mod*p[(n-i)*(m-j)]%mod;
if((i+j+k)&1)ans=(ans-res+mod)%mod;
else ans=(ans+res)%mod;
}
}
printf("%lld
",ans);
return 0;
}