zoukankan      html  css  js  c++  java
  • ZR10.1青岛集训三地联考

    ZR10.1青岛集训三地联考

    谢谢dijk和smy

    A

    题目大意:

    已知斐波那契数列(f)

    [F_i = sum_{i = 0}^nf_if_{n - i} ]

    [sum_{i = 0}^nF_i ]

    (nle 10^{18})

    首先,观察式子,我们十分容易得到一个(O(n))的算法

    因为(sum_{i = 0}^nF_i)拆开后很明显可以进行提公因式而变成

    [sum_{i = 0}^n f_isum_{n - i} ]

    (sum_i)(f)的前(i)项和

    但是可以,这种思路并不能导向正解

    一般来说,数列问题都会推一下递推式

    我们尝试推一下(F)的递推式

    [egin{aligned}F_n&=sum_{i = 0}^nf_if_{n - i} \&=sum_{i = 0}^{n - 2}f_i imes (f_{n-i-1}+f_{n-i-2}) + f_0f_n+f_1f_{n-1} \&=sum_{i = 0}^{n - 1}f_{i}f_{n - i - 1} + sum_{i = 0}^{n - 2}f_if_{n-i-2}-f_0f_{n - 1}+f_{0}f_{n}+f_1f_{n - 1}\&=F_{n - 1}+F_{n - 2}+f_n\&=F_{n - 1}+F_{n - 2}+f_{n - 1} + f_{n - 2}end{aligned} ]

    解释一下上面的小地方

    因为我们是把(f_{n - i})化为(f_{n - i - 1} + f_{n - i - 2})的形式,所以为了定义合理我们单独将(f_0f_n+f_1f_{n - 1})提出来计算

    之后我们将(sum)拆开时候发现第二项刚好是(F_{n - 2})但是第一项我们要凑(F_{i - 1})发现他少了当(i = {n - 1})时候的一项,我们强制加上,再在后面减去即可

    之后发现这个东西可以用矩阵快速幂去优化

    (left[egin{array}{ccccc}{1} & {1} & {0} & {0} & {0} \ {1} & {0} & {0} & {0} & {0} \ {1} & {1} & {1} & {1} & {0} \ {0} & {0} & {1} & {0} & {0} \ {0} & {0} & {1} & {0} & {1}end{array} ight]left[egin{array}{c}{ f_{i - 1} } \ {f_{i - 2}} \ {F_{i - 1}} \ {F_{i - 2}} \ {ans_{i - 2}}end{array} ight]=left[egin{array}{c}{f_{i}} \ {f_{i - 1}} \{F_i}\ {F_{i - 1}} \ {ans_{i - 1}}end{array} ight])

    #include<cstdio>
    #include<iostream>
    #include<queue>
    #include<algorithm>
    #include<cstring>
    #include<cctype>
    #include<vector>
    #include<ctime>
    #define LL long long
    #define pii pair<int,int>
    #define mk make_pair
    #define fi first
    #define se second
    using namespace std;
    const LL mod = 998244353;
    inline LL read(){
    	LL v = 0,c = 1;char ch = getchar();
    	while(!isdigit(ch)){
    		if(ch == '-') c = -1;
    		ch = getchar();
    	}
    	while(isdigit(ch)){
    		v = v * 10 + ch - 48;
    		ch = getchar();
    	}
    	return v * c;
    }
    struct Ma{
        LL a[8][8]; 
        LL  *operator[](int i){return a[i];}
        inline void clear(){memset(a,0,sizeof(a));} 
        friend Ma operator * (Ma x,Ma y){
            Ma c;c.clear();
            for(int i = 0;i < 5;++i)
                for(int j = 0;j < 5;++j)
                    for(int k = 0;k < 5;++k)
                        c.a[i][j] = (c.a[i][j] + (x.a[i][k] * y.a[k][j])) % mod;
            return c;
        }
    };
    inline Ma quick(Ma x,LL y){
        Ma res;res.clear();
        for(int i = 0;i < 5;++i) res[i][i] = 1;
        while(y){
            if(y & 1) res = res * x;
            y >>= 1;
            x = x * x;  
        }
        return res;
    }
    Ma ans;
    Ma dd;
    LL n;
    int main(){
    	n = read();
    	if(n == 0){
    		printf("1
    ");
    		return 0;	
    	}
    	if(n == 1){
    		printf("3
    ");
    		return 0;
    	}
    	if(n == 2){
    		printf("8
    ");
    		return 0;	
    	}
    	ans.clear();
    	ans[0][0] = 1;ans[0][1] = 1;
    	ans[1][0] = 1;
    	ans[2][0] = ans[2][1] = ans[2][2] = ans[2][3] = 1;
    	ans[3][2] = 1;
    	ans[4][2] = ans[4][4] = 1;
    ans = quick(ans,n - 1);
    	dd.clear();
    	dd[0][0] = 2;
    	dd[1][0] = 1;
    	dd[2][0] = 5;
    	dd[3][0] = 2;
    	dd[4][0] = 3;
    	dd = ans * dd;
    	printf("%lld
    ",dd[4][0]);
        return 0;
    }
    
    

    B

    题目大意

    给定一棵(n)个点的树和(m)条路径,选定某一点为根时,在每个非叶节点的所有连向儿子的边中选择一个特殊边,最大化所有路径经过的特殊边的和(原题是最小化未经过的和,反正补集关系)(n,mle 10^5)

    首先,路径经过次数我们可以用简单的树上差分来实现,我们设差分完成后(sum_i)表示第(i)条边被经过的次数,另外,我们为了方便去维护边上的信息,把边上的信息放到连接的深度较低的点上去维护

    那么如何求取最终答案?

    二次扫描

    提示:本方法的正确性有待证明,因过程中一个细节没有考虑,经smy推理后得出结论不会对最优解产生影响,尽管如此,这种方法的思路在本质上可能出现错误.

    按照二次扫描的套路,我们要维护子树内和子树外的信息

    我们设(f1_i)表示(i)子树内$sum $ 最大的一条边对应的儿子,同理设(f2_i)表示(i)子树内次大值(用这种方法维护次大值应该也是常见的操作)

    我们设(h_i)表示以(i)为根的子树中的最大贡献,这个应该是可以通过简单的树形dp求出(h_i = f1_i + sum_{j in son_i}h_j)

    我们设(g_i)表示(i)子树外的贡献,那么应该如何计算(g)数组呢

    uwMgeJ.jpg

    这是当(y)(f_1)指向的儿子的情况,不是同理

    注意这时候的(g_i + h_i)并不是以(i)为根时的答案,至于表示的什么等我去问下.

    换根法

    这才是这道题的正确做法

    我们考虑上面做法的缺陷在哪里

    尝试考虑这种情况

    这也就是我说上面的式子并不是表示以(i)为根的答案的原因,他可能会出现(i)所连接的所有的边选择两次的情况

    之后发现,只有(i)所连接的会出错

    我们考虑吧这部分的贡献减掉

    那么上面的式子我们稍微改动一下

    我们设(ans_i)表示以(i)为根时的答案

    uwQOuF.jpg

    转移比较简单

    但是有一个非常重要的坑点

    在换根的时候要更新最大值和次大值

    代码可能以后会写吧,这里只放二次扫描的代码

    #include<cstdio>
    #include<iostream>
    #include<queue>
    #include<algorithm>
    #include<cstring>
    #include<cctype>
    #include<vector>
    #include<ctime>
    #define LL long long
    #define pii pair<LL,LL>
    #define mk make_pair
    #define fi first
    #define se second
    using namespace std;
    const int N = 2e5 + 3;
    const LL INF = 1e15;
    struct edge{
    	int to;
    	int nxt;	
    }e[N << 1];
    LL sum[N];int head[N],deep[N];
    pii f1[N],f2[N];
    LL hh[N];
    LL g[N];
    LL gg[N];
    int fa[21][N];
    int n,m,tot;
    inline void add(int x,int y){
    	e[++tot].to = y;
    	e[tot].nxt = head[x];
    	head[x] = tot;	
    }
    inline int read(){
    	int v = 0,c = 1;char ch = getchar();
    	while(!isdigit(ch)){
    		if(ch == '-') c = -1;
    		ch = getchar();
    	}
    	while(isdigit(ch)){
    		v = v * 10 + ch - 48;
    		ch = getchar();
    	}
    	return v * c;
    }
    inline void dfs(int x,int f){	
    	fa[0][x] = f;
    	deep[x] = deep[f] + 1;
    	for(int i = head[x];i;i = e[i].nxt){
    		int y = e[i].to;
    		if(y == f) continue;
    		dfs(y,x);	
    	}
    }
    struct Q{
    	int xi;
    	int yi;	
    }q[N];
    inline int LCA(int x,int y){
    	if(deep[x] < deep[y]) swap(x,y);
    	for(int i = 19;i >= 0;--i)
    		if(deep[fa[i][x]] >= deep[y]) x = fa[i][x];
    		//printf("xxx:%d %d
    ",x,y);
    	if(x == y) return x;
    	for(int i = 19;i >= 0;--i)
    		if(fa[i][x] != fa[i][y]) x = fa[i][x],y = fa[i][y];
    		return fa[0][x];
    }
    inline int getsum(int x,int f){
    	for(int i = head[x];i;i = e[i].nxt){
    		int y = e[i].to;
    		if(y == f) continue;
    		getsum(y,x);
    		sum[x] += sum[y];	
    	}
    }
    inline void work1(int x,int f){
    	bool ok = 1;
    	pii as1 = mk(0,0),as2 = mk(0,0);
    	for(int i = head[x];i;i = e[i].nxt){
    		int y = e[i].to;
    		if(y == f) continue;
    		ok = 0;
    		work1(y,x);
    		if(sum[y] > as1.fi) as2 = as1,as1 = mk(sum[y],y); 
    		else if(sum[y] > as2.fi) as2 = mk(sum[y],y);
    		hh[x] += hh[y]; 
    	//	if(f1[y].fi + sum[y] > as1.fi){
    	//		as2 = as1;
    	//		as1 = mk(f1[y].fi + sum[y],y);	
    	//	}
    	//	else{
    	//		if(f1[y].fi + sum[y] > as2.fi) as2 = mk(f1[y].fi + sum[y],y);	
    	//	}
    	} 
    	hh[x] += as1.fi;
    	//if(ok) f1[x] = mk(0,0);
    	//else 
    	f1[x] = as1,f2[x] = as2;
    }
    inline void work2(int x,int f){
    	for(int i = head[x];i;i = e[i].nxt){
    		int y = e[i].to;
    		if(y == f) continue;
    		if(y == f1[x].se) g[y] = g[x] - sum[x] + hh[x] - hh[y] - f1[x].fi + max(sum[x],f2[x].fi) + sum[y];
    		else g[y] = g[x] - sum[x] + hh[x] - hh[y] - f1[x].fi + max(sum[x],f1[x].fi) + sum[y];
    		work2(y,x);
    	}
    }
    int main(){
    	//	freopen("B.in","r",stdin);
    	//freopen("B.out","w",stdout);
    	LL tt = 0;
    	n = read(),m = read();
    	for(int i = 1;i < n;++i){
    		int x = read(),y = read();
    		add(x,y);
    		add(y,x);
    	}
    	dfs(1,0);
    	for(int j = 1;j <= 20;++j){
    		for(int i = 1;i <= n;++i)
    		 fa[j][i] = fa[j - 1][fa[j - 1][i]];	
    	}
    	for(int i = 1;i <= m;++i){
    		q[i].xi = read(),q[i].yi = read();
    		int L = LCA(q[i].xi,q[i].yi);
    	//	printf("lca::%d
    ",L);
    		sum[q[i].xi]++,sum[q[i].yi]++,sum[L] -= 2;
    		tt += deep[q[i].xi] + deep[q[i].yi] - 2 * deep[L];
    	}
    	getsum(1,0);
    //	printf("%lld
    ",tt);
    //	memset(f,0x3f,sizeof(f));memset(g,0x3f,sizeof(g));
    //	for(int i = 1;i <= n;++i) tt += sum[i];
    	g[1] = 0;
    	work1(1,0);
    	for(int i = 1;i <= n;++i) printf("%lld ",hh[i]);puts("");
    //	for(int i = 1;i <= n;++i) printf("%lld %lld %lld %lld
    ",f1[i].fi,f1[i].se,f2[i].fi,f2[i].se);
    	work2(1,0);
    	for(int i = 1;i <= n;++i) printf("%lld ",g[i]); puts("");
    	LL ans = 0;
    	for(int i = 1;i <= n;++i) ans = max(ans,hh[i] + g[i]);
    	printf("%lld
    ",tt - ans);
        return 0;
    }
    
    

    C

    题目大意:一副牌,有(n)种花色,第(i)种有(a_i)张,随机打乱后,前(x)张给小E,接着(y)张给小F,求存在一种花色,使得小E手中该花色比小F中该花色的牌多(z)张的概率

    首先,题目中的取牌机制相当于小E随机取(x)张,小F再随机取(y)

    我们将概率转化为求总方案数

    接下来我们要求的就是小E和小F有多少种选法满足上述条件

    首先设(s)为各花色牌的数量之和,总方案数为(inom{s}{x} inom{s - x}{y})

    接下来想如何求得合法方案数

    接下来想,我们考虑花色(c)为 小E的制胜方案(也就是选择花色(c)使得小E制胜的方案数)

    我们想,假设小E选择了(ec)(c)花色的牌,我们枚举小F选择了(fc)(c)花色的牌,那么(c)花色可以取胜的方案数是

    [sum_{fc =0,fc + ec<=a_c,fc + zle ec} inom{a_c - ec}{fc}inom{s - (a_c - ec) - x}{y - fc} ]

    这样还不是最终答案,因为我们强制枚举一种颜色合法的过程中,会出现另外一种也合法的情况,就会导致我们多算一部分的贡献

    那么怎么去重呢

    我们想,我们出现这种情况,就是同时存在两个花色他们选择的都是合法的

    那么我们就考虑

    如何去除这种情况,我们把所有的情况按照选择的(ec)排序这样就能背包够DP的顺序

    我们在DP的时候,为了强制确定当前花色为答案,要给其他花色制定一个顺序,因为我们已经按照(ec)排了序

    这样保证了(DP)的时候每种牌能够选择的数量是单调的

    这样我们就设(f_{i,j})表示前(i)中花色选择了(j)张的方案数

    DP的时候也是要转移

    [f_{i,j} = sum_{i = 0}^{limit_i}inom{a_i}{i} imes f_{i - 1,x - i} ]

    我们DP玩这种情况之后,该花色的牌的上限(limit)也顺便更新

    #include<cstdio>
    #include<iostream>
    #include<queue>
    #include<algorithm>
    #include<cstring>
    #include<cctype>
    #include<vector>
    #include<ctime>
    #define LL long long
    #define pii pair<LL,LL>
    #define mk make_pair
    #define fi first
    #define se second
    using namespace std;
    const LL Mod = 998244353;
    const long long mod = 998244353;
    struct node{
    	__int128 val;
    	LL id;
    	LL num;	
    };
    long long dp[505][505];
    int a[505];
    __int128 c[505][505];
    int n,cnt,sum,xx,yy,zz;
    node d[500005];
    int lim[505];
    inline long long quick(long long x,long long y){
    	long long res = 1;
    	while(y){
    		if(y & 1) res = res * x % mod;
    		x = x * x % mod;
    		y >>= 1;
    	}
    	return res;
    }
    inline bool cmp(node x,node y){
    	if(x.val != y.val)
    	return x.val < y.val;
    	return x.num < y.num;	
    }
    inline long long up(long long x){
    	if(x >= mod) x -= mod;	
    	return x;
    }
    inline void print(LL x){
    	if(x == 0) return ;
    	print(x / 10);
    	putchar(x % 10 + '0');
    }
    int main(){//xx = read(),yy = read(),zz = read();
    	scanf("%d%d%d%d",&n,&xx,&yy,&zz);
    	for(int i = 1;i <= n;++i) scanf("%d",&a[i]),sum += a[i];
    	c[0][0] = 1;
    	for(int i = 1;i < 105;++i){
    		c[i][0] = c[i][i] = 1;
    		for(int j = 1;j < i;++j)
    		c[i][j] = c[i - 1][j] + c[i - 1][j - 1];	
    	}
    //	for(int i = 1;i <= 5;++i){
    //		for(int j = 0;j <= i;++j) print(c[i][j]);//,putchar(' ');
    		//putchar('
    ');
    //	} 
    //	cerr << n << endl;
    	for(int cc = 1;cc <= n;++cc){
    		for(int j = 0;j <= min(xx,a[cc]);++j){
    			d[++cnt].id = cc;
    			for(int i = 0;i + zz <= j && i + j <= a[cc] && i <= yy;++i){
    				d[cnt].val += c[a[cc] - j][i] * c[sum - (a[cc] - j) - xx][yy - i];	
    //				puts("zls nb");
    			}
    			d[cnt].num = j;
    	//		print(d[cnt].val);putchar(' ');
    		}
    	}
    	//puts("GG");
    	for(int i = 1;i < 105;++i){
    		for(int j = 0;j < 105;++j){
    			c[i][j] %= Mod;
    		}
    	}
    	long long ans = 0;
    	sort(d + 1,d + cnt + 1,cmp);
    	memset(lim,-1,sizeof(lim));
    	for(int i = 1;i <= cnt;++i){
    	//	printf("%d
    ",i);
    		memset(dp,0,sizeof(dp));
    		dp[0][0] = 1;	
    		for(int j = 1;j <= n;++j){
    			if(j == d[i].id){
    				for(int x = 0;x <= xx;++x) dp[j][x] = dp[j - 1][x];
    			}
    			else{
    				for(int x = 0;x <= xx;++x){
    					for(int y = 0;y <= min(x,lim[j]);++y){
    						dp[j][x] = up(dp[j][x] + dp[j - 1][x - y] * c[a[j]][y] % mod);	
    					}
    				}
    			}
    		}
    //		print(d[i].val); putchar('
    ');
    		ans = up(ans + (d[i].val % Mod) * dp[n][xx - d[i].num] * c[a[d[i].id]][d[i].num] % mod);
    		lim[d[i].id] = d[i].num; 
    	}
    //	cout << ans <<endl; 
    //	printf("%lld
    ",ans);
    //cout << ans * quick(c[sum][xx],mod - 2) % mod * quick(c[sum - xx][yy],mod - 2) % mod << endl;
    	printf("%lld
    ",ans * quick(c[sum][xx],mod - 2) % mod * quick(c[sum - xx][yy],mod - 2) % mod);
        return 0;
    }
    
    
  • 相关阅读:
    iOS高级教程:处理1000张图片的内存优化
    [转]改变UITextField placeHolder颜色、字体
    pushViewController自定义动画http://blog.csdn.net/ralbatr/article/details/22039233
    获取UIWebView的内容高度
    iOS: 计算 UIWebView 的内容高度
    iOS UIWebView 获取内容实际高度,关闭滚动效果
    UIImage 裁剪图片和等比列缩放图片
    Jqueryui+easyui+easywidgets做的后台界面
    多线程中使用HttpContext.Current为null的解决办法
    jquery图片上传前预览剪裁
  • 原文地址:https://www.cnblogs.com/wyxdrqc/p/11616725.html
Copyright © 2011-2022 走看看