Ryoku 的新年欢乐赛

前言

220菜鸡苟且偷生进入前三十


洛谷 6033 Ryoku 的探索

题目

有一个\(n\)个点\(n\)条边的无向连通图,每条边有长度和美观度(美观度各不相同),
若从点\(x\)出发,每次选择美观度最大的一条边走,如果没有未走过的点就瞬移到前一个点,
问从每个点出发经过全部点的长度


分析

如果没有\(n\)条边的限制就是一个基环树,那么只会有一条边而且必然是环上的边不会被算上,
而且这条边一定是与该点所在的树的根节点所连接,
所以思路特别清晰,拓扑排序判环,然后对于环上的点选取美观度小的一条环上边不选,
那么答案就是边长度总和减去这条边的长度,然后再把这个点的信息复制到子树上,
总时间复杂度\(O(n)\)


代码

#include <cstdio>
#include <cctype>
#include <queue>
#define rr register
using namespace std;
const int N=1000011; long long sum; queue<int>q;
struct node{int y,w,f,next;}e[N<<1];
int n,k=1,deg[N],ls[N],ans[N]; bool v[N];
inline signed iut(){
    rr int ans=0; rr char c=getchar();
    while (!isdigit(c)) c=getchar();
    while (isdigit(c)) ans=(ans<<3)+(ans<<1)+(c^48),c=getchar();
    return ans;
}
inline void print(long long ans){
    if (ans>9) print(ans/10);
    putchar(ans%10+48);
}
inline void dfs(int x,int now){
    ans[x]=now;
    for (rr int i=ls[x];i;i=e[i].next)
    if (v[e[i].y]&&!ans[e[i].y]) dfs(e[i].y,now);
}
signed main(){
    n=iut();
    for (rr int i=1;i<=n;++i){
        rr int x=iut(),y=iut(),w=iut(),f=iut();
        e[++k]=(node){y,w,f,ls[x]},ls[x]=k,sum+=w;
        e[++k]=(node){x,w,f,ls[y]},ls[y]=k,deg[x]++,deg[y]++;
    }
    for (rr int i=1;i<=n;++i) if (deg[i]==1) q.push(i);
    while (q.size()){
        rr int x=q.front(); v[x]=1; q.pop();
        for (rr int i=ls[x];i;i=e[i].next)
        if (deg[e[i].y]>1){
            --deg[e[i].y];
            if (deg[e[i].y]==1) q.push(e[i].y);
        }
    }
    for (rr int root=1;root<=n;++root)
    if (!v[root]){
        rr int p1=0,p2=0,p;
        for (rr int i=ls[root];i;i=e[i].next)
        if (!v[e[i].y]){if (p1) p2=i; else p1=i;}
        p=e[p1].f<e[p2].f?e[p1].w:e[p2].w,dfs(root,p);
    }
    for (rr int i=1;i<=n;++i) print(sum-ans[i]),putchar(10);
}

洛谷 6034 Ryoku 与最初之人笔记

题目

\[\sum_{i=0}^n\sum_{j=i+1}^n[i\equiv j\pmod {i\,xor\,j}] \]


分析O(log^2n)

想要\(i\)\(j\)在取模\(i\,xor\,j\)的情况下相等,不妨设\(i-x(i\,xor\,j)=j-y(i\,xor\,j)(x<y)\)
那么\((y-x)(i\,xor\,j)=j-i\)也就是\(i\,xor\,j|j-i\)
然而思路行不通了?
通过\(xor\)本质就是不退位的减法,所以\(i\,xor\,j\geq j-i\)
那么两个式子综合起来就是\(i\,xor\,j=j-i\)
什么时候两者相等呢,就是没有出现\(j\)某一位为0且\(i\)某一位是1的情况,
那么不就是只要\(i\)是1,\(j\)必须是\(1\),那不就是\(i|j=j\)(我还打表找规律)
那我可以枚举\(j(1\sim n)\),那么所对应的个数就是2的\(j\)的二进制位为1的个数次方减去1(相同不能统计)
那么\(O(n)\)方法就出来了,恭喜TLE(\(n\leq 10^{18}\)
一般这个范围我都会想数位\(dp\)的呀,那可以统计\(1\sim n\)中二进制位为1的个数为\(x\)的有多少个,
然而我只想到记录当前1的个数,然后如果没有顶着上界就可以统计之后的答案,否则继续搜索,还不用记忆化,统计答案要用到组合数
时间复杂度\(O(log^2n)\),还显得绰绰有余


代码(赛时AC)

#include <cstdio>
#define rr register
using namespace std;
typedef long long lll;
const lll mod=1e9+7;
lll n,ans,d[63],dig[63],dp[63],c[63][63],lenn;
inline lll mo(lll x,lll y){return x+y>=mod?x+y-mod:x+y;}
inline void dfs(int len,bool limit,int now){
    if (!len) {dp[now]=mo(dp[now],1); return;}
    rr int mx=limit?dig[len]:1;
    for (rr int i=0;i<=mx;++i)
    if (limit&&i==mx) dfs(len-1,limit,now+i);
    else{
        for (rr int i=0;i<=len-1;++i)
            dp[now+i]+=c[len-1][i];
    }
}
signed main(){
    scanf("%lld",&n),d[0]=c[0][0]=1,ans=!(n%mod)?0:(mod-n%mod);
    for (rr int i=1;i<=62;++i) d[i]=mo(d[i-1],d[i-1]),c[i][0]=c[i][i]=1;
    for (rr int i=2;i<=62;++i)
    for (rr int j=1;j<i;++j) c[i][j]=mo(c[i-1][j],c[i-1][j-1]);
    for (;n;n>>=1) dig[++lenn]=n&1; dfs(lenn,1,0);
    for (rr int i=1;i<=lenn;++i) ans=mo(ans,1ll*dp[i]*d[i]%mod);
    return !printf("%lld",ans);
}

分析O(logn)

比完赛我去看出题人解题报告,怎么这么短
考虑一下组合数本质
\(C(n,0)*2^0+C(n,1)*2^1+C(n,2)*2^2+\cdots+C(n,n)*2^n=C(n,n)*2^0+C(n,n-1)*2^1+C(n,n-2)*2^2+\cdots+C(n,0)*2^n=(1+2)^n=3^n\)
所以可以把后面那一坨用简单的单项式代替,时间复杂度\(O(log_2n)\)


代码(赛后)

#include <cstdio>
#define rr register
using namespace std;
typedef long long lll;
const lll mod=1e9+7;
lll n,ans,p3[63],p2[63],len;
inline lll mo(lll x,lll y){return x+y>=mod?x+y-mod:x+y;}
signed main(){
    scanf("%lld",&n),p2[0]=p3[0]=1;
    for (rr int i=1;i<=62;++i) p3[i]=mo(mo(p3[i-1],p3[i-1]),p3[i-1]);
    for (rr int i=1;i<=62;++i) p2[i]=mo(p2[i-1],p2[i-1]);
    for (rr int i=62;~i;--i) if ((n>>i)&1){//如果这一位是1统计0的答案
        ans=mo(ans,1ll*p2[len]*p3[i]%mod),++len;
    }
    ans=mo(ans,mo(mod-n%mod,p2[len]-1));//首先在前面分析说了要1到n都减去1,所以要减n,而且最后加上2的len次方-1是因为算上$n$自己
    return !printf("%lld",ans);
}

洛谷 6035 Ryoku 的逆序对

题目

给定一个数组\(B\)\(B_i=\sum_{i<j}[A_i>A_j]\)\(A\)数组是一个\(1\sim n\)的排列,然而有些B被清空了,问A的可能方案数和\(A\)字典序最小的方案


分析

首先\(B\)\(A\)一一对应,但如果\(B_i>n-i\)那肯定是不可能的,然后方案数就是\(\prod_{i=1}^n[B_i==-1]*(n-i+1)\)(早知道打表找规律了)
\(B_i=-1\)的位置变成0就能让字典序最小,然后树状数组+二分解决


代码

#include <cstdio>
#include <cctype>
#define rr register
using namespace std;
const int mod=1000000007,N=1000011;
int n,a[N],c[N],ans=1;
inline signed iut(){
    rr int ans=0,f=1; rr char c=getchar();
    while (!isdigit(c)) f=(c=='-')?-f:f,c=getchar();
    while (isdigit(c)) ans=(ans<<3)+(ans<<1)+(c^48),c=getchar();
    return ans*f;
}
inline void print(int ans){if (ans>9) print(ans/10); putchar(ans%10+48);}
inline signed query(int x){rr int ans=0; for (;x;x-=-x&x) ans+=c[x]; return ans;}
inline void add(int x){for (;x<=n;x+=-x&x) --c[x];}
signed main(){
    n=iut();
    for (rr int i=1;i<=n;++i){
        a[i]=iut();
        if (a[i]>n-i) ans=0;
        else if (a[i]==-1) ans=1ll*ans*(n-i+1)%mod,a[i]=0;
    }
    print(ans); if (!ans) return 0;
    for (rr int i=1;i<=n;++i) c[i]=-i&i;
    for (rr int i=1;i<=n;++i){
        rr int l=1,r=n;
        while (l<r){
            rr int mid=(l+r+1)>>1;
            if (query(mid-1)<=a[i]) l=mid;
                else r=mid-1;
        }
        putchar(i==1?10:32),print(l),add(l);
    }
    return 0;
}

洛谷 6036 Ryoku 爱学习

题目


分析

一看到概率一般都是期望dp了,但是怎么求这就是一个问题
首先\(a^b\)把它变成常数\(A\)(毒瘤出题人多项式插值)
\(dp[i]\)表示前\(i\)个知识的答案
不选\(i\)肯定要加上\((1-p[i])*dp[i-1]\)
但是选了\(i\)就很难计算,那得再开一个
\(s[i]\)表示前\(i\)种知识选择\(i\)的答案,
那么\(s[i]=p[i]*(1-p[i-1])*w[i]*A^0+p[i]*p[i-1]*(1-p[i-2])*(w[i]+w[i-1])*A^1+\cdots\)
首先这一坨也是很难表示的,把有关\(w[i]\)的部分提出来就是

\[s[i]=p[i]*(1-p[i-1])*w[i]+p[i]*p[i-1]*(1-p[i-2])*w[i]*A+\cdots \]

然后没有写的部分正好还缺了\(s[i-1]*A\)
再把刚刚的结合一下可以得到

\[s[i]=p[i]*(s[i-1]*A+w[i]*((1-p[i-1])*A^0+p[i-1]*(1-p[i-2])*A^1+\cdots)) \]

然而后面这一坨同样可以用\(pr[i]\)表示
先把\(s[i]=p[i]*(s[i-1]*A+w[i]*pr[i])\)
那么\(pr[i]=A*p[i-1]*pr[i-1]+1-p[i-1]\)
所以就可以\(O(n)\)解决了


代码(滚动数组+double快读)

#include <cstdio>
#include <cctype>
#include <cmath>
#define rr register
using namespace std;
const int N=100011;
int n,w[N]; double ans,A,DP,dp,Pr,pR,S,s,P,p;
inline double iut(){
	rr int ans1=0,ans2=0,len=1; rr char c=getchar();
	while (!isdigit(c)) c=getchar();
	while (isdigit(c)) ans1=(ans1<<3)+(ans1<<1)+(c^48),c=getchar();
	if (c=='.'){
		c=getchar();
		while (isdigit(c)) len=len*10,ans2=ans2*10+(c^48),c=getchar();
		return ans1+ans2*1.0/len;
	}else return ans1;
}
signed main(){
    n=(int)iut(),A=iut(),A=pow(A,iut());
    for (rr int i=1;i<=n;++i) w[i]=(int)iut();
    for (rr int i=1;i<=n;++i){
        P=iut(),Pr=A*p*pR+1-p,S=P*(A*s+Pr*w[i]),
        DP=(1-P)*dp+P*(dp-s)+S,dp=DP,p=P,pR=Pr,s=S;
    }
    return !printf("%lf",DP);
}
posted @ 2020-02-02 14:04  lemondinosaur  阅读(234)  评论(0编辑  收藏  举报