ZR10.1青岛集训三地联考

谢谢dijk和smy

A

题目大意:

已知斐波那契数列\(f\)

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

\[\sum_{i = 0}^nF_i
\]

\(n\le 10^{18}\)

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

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

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

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

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

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

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

\[\begin{aligned}F_n&=\sum_{i = 0}^nf_if_{n - i} \\&=\sum_{i = 0}^{n - 2}f_i\times (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[\begin{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}\right]\left[\begin{array}{c}{ f_{i - 1} } \\ {f_{i - 2}} \\ {F_{i - 1}} \\ {F_{i - 2}} \\ {ans_{i - 2}}\end{array}\right]=\left[\begin{array}{c}{f_{i}} \\ {f_{i - 1}} \\{F_i}\\ {F_{i - 1}} \\ {ans_{i - 1}}\end{array}\right]\)

#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\n");
return 0;
}
if(n == 1){
printf("3\n");
return 0;
}
if(n == 2){
printf("8\n");
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\n",dd[4][0]);
return 0;
}

B

题目大意

给定一棵\(n\)个点的树和\(m\)条路径,选定某一点为根时,在每个非叶节点的所有连向儿子的边中选择一个特殊边,最大化所有路径经过的特殊边的和(原题是最小化未经过的和,反正补集关系)\(n,m\le 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\)数组呢

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

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

换根法

这才是这道题的正确做法

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

尝试考虑这种情况

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

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

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

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

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

转移比较简单

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

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

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

#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\n",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\n",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\n",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\n",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\n",tt - ans);
return 0;
}

C

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

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

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

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

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

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

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

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

\[\sum_{fc =0,fc + ec<=a_c,fc + z\le ec} \binom{a_c - ec}{fc}\binom{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}\binom{a_i}{i}\times 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('\n');
// }
// 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\n",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('\n');
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\n",ans);
//cout << ans * quick(c[sum][xx],mod - 2) % mod * quick(c[sum - xx][yy],mod - 2) % mod << endl;
printf("%lld\n",ans * quick(c[sum][xx],mod - 2) % mod * quick(c[sum - xx][yy],mod - 2) % mod);
return 0;
}

ZR10.1青岛集训三地联考的更多相关文章

  1. 三校联考 Day3

    三校联考 Day3 大水题 题目描述:给出一个圆及圆上的若干个点,问两个点间的最远距离. solution 按极角排序,按顺序枚举,显然距离最远的点是单调的,线性时间可解出答案. 大包子的束缚 题目描 ...

  2. HGOI20180823 三校联考

    首测:220qwq(算差的好吧) 后来改了一个地方:300qwq(算慢的好吧) std被踩qwq 注意:输入数据第一行忘记输入n,亲脑补 题解: 多项式除法(若最后除出的答案为1那么就是成功),对于f ...

  3. NOIp2018集训test-10-6/test-10-7 (联考五day1/day2)

    昨天考完月考,明天初赛,dcoi2017级今天终于开始停课准备noip了,大概没有比本弱校停课更晚的学校了吧.本来就够菜了,怕是要凉透哦. DAY1 T1石头剪刀布 据说爆搜随便做,但是我觉得我的O( ...

  4. NOIp2018集训test-9-8(pm) (联考一day2)

    把T1题读错了,想了一个多小时发现不可做.然后打了t2,常数不优秀.然后去打t3,lct,结果打挂爆0了. 然后今天就爆炸了. 如果这是noip我今年就可以直接回去学常规了.学常规多好,多开心. 今天 ...

  5. NOIp2018集训test-9-7(pm) (联考一day1)

    又被辉神吊打了.今天不仅被辉神李巨吊打,还给基本上给全班垫底了. 看到T3就知道是十进制快速幂,全机房考试的当时应该就我会,结果我tm没找到递推. Orz lyc BM直接水过,Orz wys六个fo ...

  6. NOIp2018集训test-10-4/test-10-5 (联考四day1/day2)

    这个day1稍微有点毒瘤吧?? DAY1 排列 以前总是把day1t1想太复杂了翻车,差不多往正解的方向想了一下感觉不可能这么复杂这可是noipday1t1啊一定有非常简单的方法然后翻车了?? 题目转 ...

  7. 【赛时总结】NOIP2018-三校联考1024

    ◇NOIP三校联考-1024◇ 发现以前的博客写得似乎都很水……基本上都没什么阅读量QwQ 决定改过自新╰( ̄ω ̄o) 就从这篇博客开始吧~ 现场考得无地自容,看到题解才发现一些东西……(我第三题还没 ...

  8. NOIp2018集训test-9-15(联考二day1)

    T1.矩阵游戏 水题.每一行最后乘的数为x[i],每一列为y[i],暴力算第一行的列的贡献,每一行的列的贡献是公差为所有列的贡献之和的等差数列,然后每一行再乘上行的贡献求和即为答案. //Achen ...

  9. 2019十二省联考 Round 1 && 济南市市中心游记

    在这样一场毒瘤的省选中 这道题目无疑是命题人无私的馈赠 大量精心构造的部分分,涵盖了题目中所有涉及的算法 你可以利用这道题目,对你是否能够进入省队进行初步检查 经典的模型.较低的难度和不大的代码量,能 ...

随机推荐

  1. java 使用 POI 操作 XWPFDocumen 创建和读取 Office Word 文档基础篇

    注:有不正确的地方还望大神能够指出,抱拳了 老铁! 参考 API:http://poi.apache.org/apidocs/org/apache/poi/xwpf/usermodel/XWPFDoc ...

  2. 纯CSS3实现Metro Icon

    在线演示 本地下载

  3. hdu 3466 01背包变形【背包dp】

    http://acm.hdu.edu.cn/showproblem.php?pid=3466 有两个物品P,Q,V分别为 3 5 6, 5 10 5,如果先dp第一个再dp第二个,背包容量至少要为3+ ...

  4. c50决策树借款风险

    Decision Trees/ Machine Learning Durga Gaddam August 29, 2016 Objective: The objective of the articl ...

  5. 【NS2】学习点滴

    1 $ns duplex-link-op $n2 $n3 queuePos 0.5#此命令用于设置在NAM中显示的队列方向#经测试,发现: # queuePos 0.5表示包从上到下进入队列# que ...

  6. 瑞星推国内唯一Linux系统杀毒软件 国产操作系统还需国产安全软件保护

    近来在IT领域最爆炸的新闻莫过于5月20日中央国家机关政府採购中心下发通知.要求中央机关採购所有计算机类产品不同意安装Windows 8.而改用国产Linux操作系统. 此消息一出,立马引起各界关注. ...

  7. qt 中lineEdit->setText()输出double

    在qt中需要将获取到的double 值在ui界面上显示出来,便于观察.但是lineEdit控件的setText()要求的参数是string. 所以我们先要进行转化,将double 转化为string. ...

  8. @hdu - 6329@ Problem K. Transport Construction

    目录 @description@ @solution@ @accepted code@ @details@ @description@ 给定 n 个点,第 i 个点位于 (xi, yi). 在第 i ...

  9. 行为面试法(STAR)

    从过去的行为预测未来,是一种提问技巧. 情景 : Situation 当时怎么了 当时你所在团队主要销售任务是什么目标 : Task 你要干什么 当时你的销售业绩是多少行动 : Action 你都干了 ...

  10. Pytorch使用tensorboardX网络结构可视化。超详细!!!

    https://www.jianshu.com/p/46eb3004beca 1 引言 我们都知道tensorflow框架可以使用tensorboard这一高级的可视化的工具,为了使用tensorbo ...