20231008比赛总结

比赛链接

反思

A

我是弱智吧,统计答案时少统计了一列 − 25 p t s -25pts 25pts

B

我是弱智吧,一个错误的贪心把所有的样例全过了, − 75 p t s -75pts 75pts

C

出题人是弱智吧,卡对取模意义下 / 0 /0 /0,要扩域才能过 − 30 p t s -30pts 30pts

D

感觉有点妙的

题解

A

没什么好讲的,直接状压即可
注意到出题人是 s b sb sb n = 0 n=0 n=0 时数据范围不一样

#include <bits/stdc++.h>
using namespace std;
int n,m,k,x[20],y[20],c[500100];
double f[110][110][1<<10],res[1<<10];
const double eps=1e-8,inf=1000000000000000;
inline int read(){
	int FF=0,RR=1;
	char ch=getchar();
	for(;!isdigit(ch);ch=getchar()) if(ch=='-') RR=-1;
	for(;isdigit(ch);ch=getchar()) FF=(FF<<1)+(FF<<3)+ch-48;
	return FF*RR;
}
int main(){
    freopen("test.in","r",stdin);
    freopen("test.out","w",stdout);
	n=read(),m=read(),k=read();
	for(int i=0;i<n;i++) x[i]=read();
	for(int i=1;i<=m;i++) y[i]=read();
	for(int i=1;i<k;i++) c[i]=read();
    if(!n){
        double ans=0;
        int las=0;
        for(int i=1;i<=k;i++){
            int a=read(),s=read(),z=read();
            int tc=0;
            if(las) tc=c[i-las];
            double ty=1.0*y[s]*(1-1.0*tc/100.0);
            double sc=1-max(0.0,1-ty/z)*max(0.0,1-ty/z);
            ans+=sc*a;
            if(sc+eps<0.64) las=i; 
        }
        printf("%.2lf\n",ans);exit(0);
    }
    for(int S=0;S<1<<n;S++){
        double cur=1;
        for(int i=0;i<n;i++) if(S>>i&1) cur=cur*(1+1.0*x[i]/100.0);
        res[S]=cur;
    }
    for(int i=0;i<=k;i++) for(int j=0;j<=k;j++) for(int S=0;S<1<<n;S++) f[i][j][S]=-inf;
    f[0][0][0]=0;
	for(int i=1;i<=k;i++){
		int a=read(),s=read(),z=read();
		for(int j=0;j<i;j++){
			int tc=0;
            if(j) tc=c[i-j];
			for(int S=0;S<1<<n;S++)
				for(int T=S;;T=(T-1)&S){
					double ty=1.0*y[s]*(1-1.0*tc/100.0)*res[T];
					double sc=1-max(0.0,1-ty/z)*max(0.0,1-ty/z);
					if(sc+eps<0.64) f[i][i][S]=max(f[i][i][S],f[i-1][j][S^T]+sc*a);
					else f[i][j][S]=max(f[i][j][S],f[i-1][j][S^T]+sc*a);
					if(!T) break;
				}
		}
	}
    double ans=0;
    for(int i=0;i<=k;i++) for(int S=0;S<1<<n;S++) ans=max(ans,f[k][i][S]);
	printf("%.2lf\n",ans);
	fprintf(stderr,"%d ms\n",int(1e3*clock()/CLOCKS_PER_SEC));
	return 0;
}

B

如果我们把体力值化成函数图像可以发现,答案为最低点的相反数
考虑钦定子树的访问顺序,使最小的体力值最大
考虑相邻比较,如果前一棵子树的 s u m a − 2 s u m w sum_a-2sum_w suma2sumw x x x min ⁡ { f v − w i , s u m u − 2 w i } \min\{f_v-w_i,sum_u-2w_i\} min{fvwi,sumu2wi} y y y w i w_i wi 为连向子树的边的长度
考虑前一个比后一个更优即为: min ⁡ { y 1 , x 1 + y 2 } > min ⁡ { y 2 , x 2 + y 1 } \min\{y_1,x_1+y_2\}>\min\{y_2,x_2+y_1\} min{y1,x1+y2}>min{y2,x2+y1}
考虑对 x x x 的情况分类讨论:
如果 x 1 , x 2 x_1,x_2 x1,x2 正负性不同,那么 x x x 为正的排在前面
如果 x 1 , x 2 < 0 x_1,x_2<0 x1,x2<0,那么 x 1 + y 2 > x 2 + y 1 x_1+y_2>x_2+y_1 x1+y2>x2+y1 时才能把 1 1 1 排在前面
如果 x 1 , x 2 > 0 x_1,x_2>0 x1,x2>0,那么 y 1 > y 2 y_1>y_2 y1>y2 时才能把 1 1 1 排在前面
所以时间复杂度 O ( n l o g n ) O(nlogn) O(nlogn)

#include <bits/stdc++.h>
#define int long long
#define fi first
#define sn second
using namespace std;
const int N=100100;
typedef pair<int,int> pii;
int n,a[N],f[N],totv[N];
int e[N<<1],w[N<<1],ne[N<<1],h[N],idx;
inline int read(){
    int FF=0,RR=1;
    char ch=getchar();
    for(;!isdigit(ch);ch=getchar()) if(ch=='-') RR=-1;
    for(;isdigit(ch);ch=getchar()) FF=(FF<<1)+(FF<<3)+ch-48;
    return FF*RR;
}
bool cmp(const pii &x,const pii &y){
    if(x.fi<0&&y.fi>0) return false;
    if(x.fi>0&&y.fi<0) return true;
    if(x.fi<0) return x.fi+y.sn>x.sn+y.fi;
    return x.sn>y.sn;
}
void dfs(int u,int fa){
    vector<pii> vec;
    totv[u]=a[u];
    for(int i=h[u];~i;i=ne[i]){
        int v=e[i];if(v==fa) continue;
        dfs(v,u),totv[u]+=totv[v]-2*w[i];
        vec.push_back({totv[v]-2*w[i],min(f[v]-w[i],totv[v]-2*w[i])});
    }
    sort(vec.begin(),vec.end(),cmp);
    int cur=a[u];f[u]=0;
    for(pii t:vec) f[u]=min(f[u],cur+t.second),cur+=t.first;
}
void add(int x,int y,int z){ e[idx]=y,w[idx]=z,ne[idx]=h[x],h[x]=idx++;}
signed main(){
    freopen("horse.in","r",stdin);
    freopen("horse.out","w",stdout);
    n=read();
    for(int i=1;i<=n;i++) a[i]=read();
    memset(h,-1,sizeof(h));
    for(int i=1;i<n;i++){
        int x=read(),y=read(),z=read();
        add(x,y,z),add(y,x,z);
    }
    dfs(1,-1);
    printf("%lld\n",-f[1]);
    fprintf(stderr,"%d ms\n",int64_t(1e3*clock()/CLOCKS_PER_SEC));
    return 0;
}

C

O ( n 3 ) O(n^3) O(n3) 的树形 d p dp dp 是简单的
这里有一个 t r i c k trick trick p x × ( 选 x 个数的乘积 ) × ( 其他数不选的乘积 ) p^x\times(选x个数的乘积)\times(其他数不选的乘积) px×(x个数的乘积)×(其他数不选的乘积),可以把 p x p_x px 放在乘积里面,然后就可以优化掉一个 n n n 的复杂度
然后换根即可
注意到出题人故意造了 ∗ 0    / 0 *0\;/0 0/0 的数据,所以要扩域,把数表示成 a ∗ 0 b a*0^b a0b 的形式
时间复杂度 O ( n l o g n ) O(nlogn) O(nlogn)

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N=1000100,P=998244353;
struct extend{ int a,b;};
int n,p,siz[N];
int inv[N],f[N];
extend ans[N];
int e[N<<1],ne[N<<1],h[N],idx;
inline int read(){
	int FF=0,RR=1;
	char ch=getchar();
	for(;!isdigit(ch);ch=getchar()) if(ch=='-') RR=-1;
	for(;isdigit(ch);ch=getchar()) FF=(FF<<1)+(FF<<3)+ch-48;
	return FF*RR;
}
void add(int x,int y){ e[idx]=y,ne[idx]=h[x],h[x]=idx++;}
int qmi(int a,int b){
	int res=1;
	for(;b;b>>=1){
		if(b&1) res=1ll*res*a%P;
		a=1ll*a*a%P;
	}
	return res;
}
extend operator *(extend x,int y){
	if(!y) x.b++;
	else x.a=1ll*x.a*y%P;
	return x;
}
extend operator /(extend x,int y){
	if(!y) x.b--;
	else x.a=1ll*x.a*qmi(y,P-2)%P;
	return x;
}
int ask(int sz){ return (1ll*p*inv[sz]+1-inv[sz])%P;}
void dfs(int u,int fa){
	siz[u]=1;
	for(int i=h[u];~i;i=ne[i]) if(e[i]!=fa) dfs(e[i],u),siz[u]+=siz[e[i]];
    f[u]=ask(siz[u]),ans[1]=ans[1]*f[u];
}
void dfs2(int u,int fa){
	for(int i=h[u];~i;i=ne[i]){
		int v=e[i];if(v==fa) continue;
		ans[v]=ans[u]/f[v]*ask(n-siz[v]);
		dfs2(v,u);
	}
}
int main(){
    freopen("treap.in","r",stdin);
    freopen("treap.out","w",stdout);
	n=read(),p=read();
	inv[1]=1;
	for(int i=2;i<=n;i++) inv[i]=1ll*(P-P/i)*inv[P%i]%P;
    memset(h,-1,sizeof(h));
	for(int i=1;i<n;i++){
		int x=read(),y=read();
		add(x,y),add(y,x);
	}
	ans[1]={1,0},dfs(1,-1),dfs2(1,-1);
	int ANS=0;
	for(int i=1;i<=n;i++) if(!ans[i].b) ANS=(ANS+ans[i].a)%P;
	ANS=1ll*ANS*inv[n]%P;
	printf("%d\n",(ANS+P)%P);
	fprintf(stderr,"%d ms\n",int(1e3*clock()/CLOCKS_PER_SEC));
	return 0;
}

D

考虑图中的黑边为出了三元环之间的边以外的其他相隔 2 2 2 的点之间的连边
考虑到一个结论是三元环的个数是 O ( m m ) O(m\sqrt m) O(mm )
所以我们其实是可以直接通过一些方法来判掉三元环的情况的
具体来说,我们可以用两个队列在存储从 a a a 边来的点和从 b b b 边来的点,这样可以省掉一个优先队列的 l o g log log
这样的话,每个点只会拓展一次
考虑边权为 a a a 的边是好做的,直接暴力时间复杂度就是对的
对于边权为 b b b 的边,我们考虑每次枚举邻点 v v v 和邻点的邻点 2 2 2 w w w,邻点 2 2 2表示这个点是被邻点访问到时需要访问的邻点的集合
如果 w w w u u u的邻居的话,就跳过,但 w w w 需要保存在 v v v 的邻点 2 2 2 中,否则,就更新答案,且把 w w w 从邻点 2 2 2 中删去
时间复杂度 O ( m m ) O(m\sqrt m) O(mm )
很牛!!!

#include <bits/stdc++.h>
#define pb push_back
using namespace std;
typedef long long LL;
typedef pair<LL,int> pli;
const int N=150100,M=600100;
int n,m,a,b;
bool vis[N],tag[N];
LL f[N];
vector<int> G[N],T[N];
queue<int> Qa,Qb;
inline int read(){
    int FF=0,RR=1;
    char ch=getchar();
    for(;!isdigit(ch);ch=getchar()) if(ch=='-') RR=-1;
    for(;isdigit(ch);ch=getchar()) FF=(FF<<1)+(FF<<3)+ch-48;
    return FF*RR;
}
int main(){
    freopen("path.in","r",stdin);
    freopen("path.out","w",stdout);
    n=read(),m=read(),a=read(),b=read();
    for(int i=1;i<=m;i++){
        int x=read(),y=read();
        G[x].pb(y),G[y].pb(x);
        T[x].pb(y),T[y].pb(x);
    }
    memset(f,0x3f,sizeof(f));f[1]=0;
    Qa.push(1);
    while(!Qa.empty()||!Qb.empty()){
        int u;
        if(Qb.empty()||(!Qa.empty()&&f[Qa.front()]<f[Qb.front()])){ u=Qa.front();Qa.pop();}
        else{ u=Qb.front();Qb.pop();}
        if(vis[u]) continue;
        vis[u]=true;
        for(int v:G[u]){
            tag[v]=true;
            if(f[u]+a<f[v]) f[v]=f[u]+a,Qa.push(v);
        }
        for(int v:G[u]){
            vector<int> tmp;tmp.clear();
            for(int w:T[v]){
                if(vis[w]) continue;
                if(tag[w]) tmp.pb(w);
                else if(f[u]+b<f[w]) f[w]=f[u]+b,Qb.push(w);
            }
            swap(T[v],tmp);
        }
        for(int v:G[u]) tag[v]=false;
    }
    for(int i=2;i<=n;i++) printf("%lld\n",f[i]);
    fprintf(stderr,"%d ms\n",int(1e3*clock()/CLOCKS_PER_SEC));
    return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值