Logo lxn 的博客

博客

标签
暂无

ABC330_E的BIT和线段树解法

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2023-12-02 16:12:05

方法一:set

不解释,题解好多。

方法二:BIT

a桶数组。c是否出现。a[i]==0时候,放在c权值中,否则删除。

怎样查询? 二分查找前缀和为0的位置。

\/*
https:\/\/atcoder.jp\/contests\/abc330\/submissions\/48086953
written by : zjs123
QQ : 755328053
Wechat : zoujinshu18
CZOJ : zjs123
luogu : Running_For_Dream
atcoder : zajasi10
codeforces : vegetable_zajasi
*\/
#include<bits\/stdc++.h>
using namespace std;
const int N=2e5+9; 
int n,t;
int a[N],c[200020],k[200020];
 
int x,y;
int lowbit(int x){
    return x&(-x);
}
void add(int x){
    if(x>n)return;
    int y=x;
    if(k[x]==0){
        x++;
        while(x<=n){
            c[x]++;
            x+=lowbit(x);
        }
    }
    k[y]++;
}
void move(int x){
    if(x>n)return;
    int y=x;
    if(k[x]==1){
        x++;
        while(x<=n){
            c[x]--;
            x+=lowbit(x);
        }
    }
    k[y]--;
}
int find(int x){
    int re=0;
    while(x){
        re+=c[x];
        x-=lowbit(x);
    }
    return re;
}
int search(){
    if(k[0]==0)return 0;
    int l=0,r=n;
    while(l<=r){
        int mid=(l+r)\/2;
        if(find(mid+1)==mid+1)l=mid+1;
        else r=mid-1;
    }
    return l;
}
main(){
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    cin>>n>>t;
    for(int i=1;i<=n;i++){
        cin>>a[i];
        add(a[i]);
    }
    while(t--){
        cin>>x>>y;
        move(a[x]);
        a[x]=y;
        add(y);
        cout<<search()<<"\n";
    }
    return 0;
}

BIT直接二分,少一个log复杂度

\/*
https:\/\/atcoder.jp\/contests\/abc330\/submissions\/48086953
written by : zjs123
QQ : 755328053
Wechat : zoujinshu18
CZOJ : zjs123
luogu : Running_For_Dream
atcoder : zajasi10
codeforces : vegetable_zajasi
*\/
#include<bits\/stdc++.h>
using namespace std;
const int N=2e5+9;
int n,t;
int a[N],c[200020],k[200020];
\/\/a桶数组。c是否出现。a[i]==0时候,放在c权值中,否则删除。
\/\/怎样查询? 二分查找前缀和为0的位置。
int x,y;
int lowbit(int x){
    return x&(-x);
}
void add(int x){
    if(x>n)return;
    int y=x;
    if(k[x]==0){
        x++;
        while(x<=n){
            c[x]++;
            x+=lowbit(x);
        }
    }
    k[y]++;
}
void move(int x){
    if(x>n)return;
    int y=x;
    if(k[x]==1){
        x++;
        while(x<=n){
            c[x]--;
            x+=lowbit(x);
        }
    }
    k[y]--;
}
int find(int x){
    int re=0;
    while(x){
        re+=c[x];
        x-=lowbit(x);
    }
    return re;
}
int search(){
    if(k[0]==0)return 0;
    
    int _log=log2(n);
    int res = 0, sum = 0;
    for (int j = _log; j >= 0; j--) {
			if (res+(1<<j) <= n && sum + c[res+(1<<j)] == res+(1<<j))
                res += 1 << j,sum+=c[res];
        }

        return res;
}
main(){
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    cin>>n>>t;
    for(int i=1;i<=n;i++){
        cin>>a[i];
        add(a[i]);
    }
    while(t--){
        cin>>x>>y;
        move(a[x]);
        a[x]=y;
        add(y);
        cout<<search()<<"\n";
    }
    return 0;
}

方法三线段树写法

sz出现的次数。mex如果有这个数值就是1e9,没有这个数值就是最小没有出现过的数,也就是左端点。用线段树树叶维护没有出现过得左端点。其他节点维护最小值。查询根节点最小值即可。

单点修改sz,sz到了0,就修改mex。区间查询mex的最小值。

\/\/cxm代码 .超过n的数没有用,不管他。
#include <bits\/stdc++.h>
using namespace std;
int n, q, a[200010], cnt, rt;
struct node {
	int sz, mex, lson, rson;
	
} t[200010 * 160];
#define mid (l + r >> 1)
void add(int &now, int x, int y, int l = 0, int r = 1e9) {
	if (now == 0) now = ++cnt, t[now].mex = l;
	if (x < l || x > r) return;
	t[now].sz += y;
	if (l == r) {
		if (t[now].sz) t[now].mex = 1e9;
		else t[now].mex = l;
		return;
	}
	add(t[now].lson, x, y, l, mid);
	add(t[now].rson, x, y, mid + 1, r);
	t[now].mex = min(t[t[now].lson].mex, t[t[now].rson].mex);\/\/ 
}
signed main() {
	ios::sync_with_stdio(false);
	cin.tie(0), cout.tie(0);
	cin >> n >> q;
	for (int i = 1; i <= n; i++)
		cin >> a[i], add(rt, a[i], 1);
	while (q--) {
		int x, y;
		cin >> x >> y;
		add(rt, a[x], -1);
		a[x] = y, add(rt, y, 1); \/\/单点修改两次,a[x]消失,y出现。 
		cout << t[rt].mex << endl;\/\/单点查询跟结点。 
	}
	return 0;
}

动态规划视频合集

ARC169-AB

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2023-12-12 09:48:57

A:

思考的时候通过矩阵思考的。 题目翻译 给你一棵以 1为根的树,i 号点的父亲是 Pi,点权是 Ai,重复无限次,从 1∼N 分别将 i 号点的点权加进其父亲的点权。请判断1 号点点权的正负性。

推导: 设一个小的矩阵A,大小为$N*N$,变换$10^{10}$次,求变换后的矩阵$%P$

翻译题解:

如果我们遵循$P_j→P_i→⋯P_1$的顺序,我们最终会到达1。设i的深度$di$为从$i$到达1所需要的→个数。 对于每一个$j(0≤j<N)$,设$B_j$为$A_i$对所有i的和,使$d_i =j$。 首先,$a_1 = b_0$。 有一个运算可以表示为$ B_j+=$ $B_{j+1}$ $(0 \leq j < N)$。

如果所有的$b_j$都是0,那么不管我们操作多少次,$b_0$都是$0$,所以答案是$0$。

假设有一个不为零的$B_j$。现在,我们取最大的$j$使得$B_j !=0$,我们称它为$k$.

  • 考虑$B_k >0$的情况。

首先,因为$B_j (j>k)$都是$0$,所以$B_k$的值是常数。

$\space \space$特别是,它总是正的。 接下来,考虑$B_{k-1} $。这个值随着每次操作增加$B_k$,所以即使初始值是负的,在足够数量的操作之后,它总是正的。接下来,考虑$B_{k-2}$。由前面的讨论可知,经过足够次数的运算后,$B_{k-1} $总是正的。因为这个$B_{k-1} $被加到$B_{k-2} $,最终$B_{k-2} $也会变成正数。重复这个论点,我们可以看到,在足够多的操作之后,即使$B_0$也会变成正数。

因此,在这种情况下,答案是$+$。

  • 对于$B_k <0$的情况.可以类似地进行讨论,我们可以看到答案是$-$。

这里,我们使用了表达式“a enough number of times”,但是如果我们正确地计算,我们可以看到$10^{100}$是足够的。

它可以通过使用二项式系数计算每个$A_i$的贡献来完成,尽管细节被省略了。如果我们按照上面的判断执行,我们可以在$O(N)$时间内解决问题。

官方题解,以上翻译结果来自有道神经网络翻译(YNMT)·

变换转为矩阵

在思考过程中,我们可以把矩阵变换写下来,发现变换的系数二项式定理的展开项。

随着系数的增大,后面的项权值变大,原始的ai值所占比重越来越小。可以推导出前面的转移关系。

\/*
1<=pi<i, api +=ai,
首先明确了这是一颗树,不存在环。而且都是下标小的数累加上下标大的数
因为是进行无限次变换。
那么树最底层的累加会依次向上传递。每个数都收到下一层的影响。因此从最底层开始判断
如果为0那决定不了影响
如果非零那就最后影响到A1为正,为负则A1为负。 
因为小的受大的影响,实际为一个拓扑。可以写dfs,也可以写逆序循环。

可以先写一个矩阵转移变换,建立一个实际印象。
转换时候的系数变换类似二项式定理。实际是矩阵的次方的形式。 
 
5
1 -1 1 -1 1
1 1 2 2
0 1 1 2 2 后移到一个。正好把需要处理的数加上。
A1+=A[2] A1+=A[3]
A[2]+=A4,A2+=A[5]
*\/
#include<bits\/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
const int MAXN = 2.5e5 + 5;

int a[MAXN], p[MAXN], dep[MAXN];
ll bak[MAXN];

int main(void)
{
	int n;
	scanf("%d",&n);
	for(int i=1; i<=n; ++i)
		scanf("%d",&a[i]);
	for(int i=2; i<=n; ++i)
		scanf("%d",&p[i]);
	
	for(int i=2; i<=n; ++i)
		dep[i] = dep[p[i]] + 1;
	
	for(int i=1; i<=n; ++i)\/\/每层的变换可以打包合并在一起。
		bak[dep[i]] += a[i];
	for(int i=n; i>=0; --i)
		if(bak[i] != 0)
		{
			if(bak[i] > 0)
				printf("+");
			else
				printf("-");
			return 0;
		}
	printf("0");
	return 0;
}

B

要计算单个区间的$f$,用贪心算法,从左端尽可能多地获取。

要计算所有区间的$f$,从每个左端点$l$开始。

对每个端点$i$,还要找出最右侧的$r$使得$\sum_{j=i}^{j<=r}a_j<S $

对于每一个 定义$dp[i]=\sum_{j=i}^N f(A[i,j])$。为方便起见,设$dp[N+1]=0$。

因此我们要倒着求$dp[i]$,$i=N,N-1,...1$

对于一个左端点$i$,设其右侧的端点r为划分到和$\leq S$的最右侧位置。$i,i+1,...,r,r+1,... n$

  • 对于端点$j>=r$

    • 这一段作为整体贡献增加1,这样的数共有$n-r+1$个。
    • 这一段后面的贡献为 $dp[r+1]$
  • 对于端点$j<r$ 从i开始到j使得贡献怎加1.这样的$j$共有$r-i$个.

因此,总的动态规划方程为 $dp[i]=dp[r+1]+N-r+1+r-i$

$dp[i]=dp[r+1]+N+1-i$

最终的答案是$\sum_{i=1}^{i<=n} dp[i]$

状态转移示例图

参考代码

\/*
考虑一段的贡献
l    r ,以l为左端点,这段和和<=s最右侧的位置为j
如果这段被计数,那么r+1到n都统计上了。
或者右端点不超过r的这段区间
f[l]=f[r]+(r-l)+n-l+1;

*\/
#include<bits\/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
const int MAXN = 2.5e5 + 5;

ll a[MAXN];
ll sum[MAXN], dp[MAXN];
int main(void)
{
	int n; ll S;
	scanf("%d%lld",&n,&S);
	for(int i=1; i<=n; ++i)
		scanf("%lld",&a[i]);
	

	for(int i=1; i<=n; ++i)
		sum[i] = sum[i-1] + a[i];
	
	ll ans = 0;
	for(int i=n; i>=1; --i)
	{
		int j = upper_bound(sum+i, sum+n+1, sum[i-1] + S) - sum;
		dp[i] = (j-i) + dp[j] + (n-j+1);
		ans += dp[i];
	}
	
	printf("%lld\n",ans);
	return 0;
}

交互题练手

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2023-12-13 15:01:17

  • 基本模型

交互题这类型不同于普通的题。 可以理解为有个问题需要你解决,你通过输入某些东西。 表示你要问系统的问题,这时系统会回答你的问题。 在代码中的回答方式就是会输入某个东西就是系统给你的答案,通过这些信息你可以得到问题的解。 你是不可以自己测试的,只能提交给系统测试。 有个东西需要用到C++中的 fflush(stdout); ,这个东西是用来清空输出缓存区的。 因为你一直提问,一直输出,就需要清空输出缓存区。不然就有一些异常。

举一个最简单的例子: 猜数字我内心突然想到一个1到100之间的整数x。 现在我让你来猜,最多给你7次机会,每次你可以猜一个数字。 我会告诉你是大了,还是小了,还是猜对了。 方法就是二分。

CF679A Bear and Prime

  • 题目描述

这是一道交互题。 有一个在闭区间[2,100]里面的整数X,你的任务是判断整数x是质数还是合数。 你可以向系统询问一个数是否为该数的约数,询问方式如下:
1.你输出一个在闭区间[2,100]里面的整数
2.系统回答yes或者no(yes表示你输出的数是该数的约数,no表示你输出的数不是该数的约数)
注意:你最多只能提出20次询问!
每输出一个询问,你需要使用flush,下面提供一些语言的flush语法:
C++语言: fflush(stdout)
JAVA语言: System.out.flush()
Python语言: stdout.flush()
Pascal语言: flush(output)\

  • 算法分析: 找出100以内的质数,要找出其两个因子来,才能知道是合数。
77=7*11
25=5*5 
100以内的数最大为2*50,找到50以内最大的 素数47即可。

分成三种情况。

p,素数:只有1和他本身。不询问1,可能询问到本身。

p*p*p ,看要询问p*p判断是否为合数

多个因子p*q,一般情况。

参考代码:

#include<cstdio>
#include<cstring>
using namespace std;
int ask[20]= {2,3,5,7,11,13,17,19,23,29,31,37,41,43,47}; \/\/存储需要询问的数。
int cnt;\/\/因数个数。
int main() {
	for(int i=0; i<15; i++) {
		printf("%d\n",ask[i]);\/\/询问。
		fflush(stdout);
		char status[4];
		scanf("%s",&status);
		if(status[0]=='y') { \/\/询问的数是x的因数。
			cnt++;
			if(ask[i]*ask[i]<=100) { \/\/询问的数的平方有可能是x的因数。
				printf("%d\n",ask[i]*ask[i]);\/\/再询问一次。
				fflush(stdout);
				scanf("%s",&status);
				if(status[0]=='y') {
					cnt++;
				}
			}
		}
		if(cnt>=2) { \/\/因数个数大于2。
			printf("composite\n");
			return 0;
		}
	}
	printf("prime\n");
	return 0;
}

ABC337E

  • 题目大意,给定$n$种果汁,其中有一种坏了,和坏了的果汁会胃痛。你打算找最少的人数给你试吃,找出哪种坏了。试吃的适合取出x种果汁混子一起。只要有坏的,试吃的人就会胃痛。问你最少要找多少人试吃,怎么给他们分配试吃的果汁。然后根据反馈,找出坏的果汁。
  • 题目分析
n=9 
可以拿出最后一个不处理。n--; 
算法思路:m,应该与log相关,怎么相关。先想的是分奇数偶数,每组2人,分m组。
后来发现不用每组2人。
分组  n=8 
第0组          12345678 
第一组 奇数 偶数  1357   2468	 可以分成奇数偶数,假设为奇数 偶数没有用了,删掉2468 1357 
第二组             1526   3748   假设又为奇数   保留了15  删掉了37  总共删掉了234678 
第三组             1234   5678   假设为偶数    删掉了1,保留了5
第0组变成第四组 9 
写不完了,不知道对不对
     奇数   偶数  根据进制 
 1: 1111   000   000
 2   1011   100   001
 3   1101   010   010
 4   1001   110   011
 5   1110   001   100
 6   1010   101   101
 7   1100   011   110
 8   1000   111   111
 9:0000 

怎么完成对应关系?用奇数有点奇怪,用偶数正合适 。
怎样用代码实现分配呢? 
真好把对应位置上的1选出来:0-7 
2468 2^0次位1
3478 2^1次位1
5678 2^2次位1 
 
这个题目左右分组也可以,代码实现可能啰嗦一些。
  • 参考代码

#include <bits\/stdc++.h>
using namespace std;
const int MAXN = 105;
int n;
string s;
int main() {
    cin>>n;
    int m = 0;
    while ((1 << m) < n) m++;
    cout<<m<<endl;
    for (int i = 0; i < m; i++) {
        vector<int> vec;
        for (int j = 0; j < n; j++) if (j >> i & 1) {
            vec.push_back(j + 1);
        }
        cout<<vec.size()<<" ";
        for (int j : vec) cout<<j<<" ";
        cout<<endl;
    }
    \/\/fflush(stdout);\/\/结束输入。 
    cin>>s;
    int ans = 0;
    for (int i =0; i <m; i++) {
        if (s[i] == '1') ans |= 1 << i;
    }
    ans++;
    cout<<ans<<endl;
    return 0;
}

CF1167B Lost Numbers

P1947

ABC313D

ABC269E

ABC337E

参考资料:https://voycn.com/article/c-jiaohuti

AT_abc333_g [ABC333G] Nearest Fraction——数学或二分

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2023-12-19 10:33:47

ABC333G

题目大意:给定整数$N$,求$gcd(p,q)=1,p,q\leq n$,使得$|r-\frac{p}{q}|$最小。

方法1:

分治,设左侧分数为$\frac{a}{b}$,右侧分数为$\frac{c}{d}$. 那么$\frac{a}{b} < \frac{a+c}{b+d} < \frac{c}{d} $

每次判断$r$与$\frac{a+c}{b+d}$的关系,缩小区间。T个点。 这个方法慢在哪里呢? 比如$\frac{0}{1} < \frac{99}{101} < \frac{99}{100}$ 那么每次缩小的区间是很小的。 我们可以发现 $$\frac{a}{b} < \frac{a+c}{b+d}<\frac{a+2c}{b+2d}<... < \frac{c}{d} $$

方法2:

二分答案,设左侧分数为$\frac{a}{b}$,右侧分数为$\frac{c}{d}$. 那么$\frac{a}{b} < \frac{a+kc}{b+kd} < \frac{c}{d} $

再一次从过二分,找到合适得k,缩小区间。 注意数据范围本来就$10^{18}$,需要用到_int128.

方法3

//coder mhb2010: 学习来源

要想得到$\frac{p}{q}$,只要能得到$\frac{q}{p}$就可以推导出来。 例如:$N=100,r=0.31=\frac{31}{100}$那我们通过求解$\frac{100}{31}$来完成。

$$ \frac{1}{\frac{100}{31}} $$ $$\frac{1}{3+\frac{7}{31}}$$

$$\frac{1}{3+\frac{1}{\frac{31}{7}}}$$

$$ \frac{1}{3+\frac{1}{4+\frac{3}{7}}}$$ 写字试试<\font> $$
\frac{1}{3+\frac{1}{4+\frac{1}{2+\frac{1}{3}}}}
$$

把这个式子倒着推上去,会得到原来的而分数。这里要求的数值要小于$p,q<N$。 那么右下方的数值的大小就是可以忽略到的误差。忽略的越多$p,q越小$。 每次求得的$y/x$就是前面二分得到的 $k$ .不知道怎么证明。

疑问

为什么每次 $\frac{p}{q}=\frac{a+kc}{b+kd}$得到的$p,q$一定是互质的?

  • 这是写给自己看的学习笔记。暂且放啊题解上,欢迎大家给我答疑。

[ABC229F] Make Bipartite-图论+DP+环形

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2024-01-05 09:14:57

题目大意:

给定一个类似从中心的切三角形西瓜的图,你删去一些边,使图成为二分图,且删去边权和最小。

做法:

图论转环形dp

最小删边数,保留的最大。不断增加

假设0点染0色

f[i][0/1]代表处理1..i个点,i个点与染什么颜色。 从离散的点开始加边。

确定起点。


#include<bits\/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e6+9; 
ll a[N],b[N],n,f[N][2],ans,s=0; 
void work(int flag,ll x){
	memset(f,-32,sizeof(f)); \/\/初始化,求最大值用最小值初始化。(写错丢分) 
	f[1][flag]=x;\/\/不能与0点相连的  
	for(int i=2; i<=n; ++i)
    {
        f[i][0] = max(f[i-1][0], f[i-1][1] + b[i-1]);\/\/可以与染1色的相连 
        f[i][1] = max(f[i-1][0] + b[i-1] , f[i-1][1]) + a[i];\/\/可以与染0的i-1点相连。还可以与0点相连 
    }
    return ;
} 
int main(){

	cin>>n;
	for(int i=1;i<=n;i++) scanf("%lld",&a[i]),s+=a[i];
	for(int i=1;i<=n;i++) scanf("%lld",&b[i]),s+=b[i];
	work(0,0);
	ans=max(f[n][0],f[n][1]+b[n]);
	work(1,a[1]);
	ans=max(ans,max(f[n][1],f[n][0]+b[n]));
	
	cout<<s-ans<<endl;	
} 

[ABC285E] Work or Rest-环形背包

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2024-01-05 09:38:47

题目大意; 某国一个礼拜有$N$ 天。国王高桥准备把每一天安排为 节假日或者工作日。如果$i$号是工作日,那么动力是 $min(x,y)$,其中 $x$ 和 $y$ 是它离前一个或后一个节假日的天数。分配休息日,求出每周$N$天的生产值之和最大可以是多少。

拿到题目,感觉无从下手。
先枚举,假设只有1天休息,就枚举是哪天休息。
根据题意,只与距离前后休息的天数有关。与具体哪天休息无关。
$。。。。。。 16,25,43,34,52,61 ,$ 设一段工作日为$m$,那么$s=2\sum a\frac m 2 $
$。。。。。15,24,33,42,51 $奇数偶数不同。
可以统计出$1...n$连续工作时间的效率值$s_i$。
设每段背包状态为$。。。* $。
设定第一天为休息日。做完全背包 背包总体积为$n;1(.。。) (。。。)$最后一个和第一个*重合 有n-1个物品, 体积为1到n,价值为s0到sn-1 细节处理,要和开头环起来,

1:0
2:11          a1         
3  12 21       2a1
4 13 22 31     2a1+a2
5 14 23 32 41  2a1+2a2
6              2a1+2a2+a3
7              2a1+2a2+2a3

- 参考代码

#include<bits\/stdc++.h>
using namespace std;
typedef long long ll;
const int N=5e3+9; 
ll n,a[N],w[N],f[N]; 

int main(){

	cin>>n;
	for(int i=1;i<=n;i++) scanf("%lld",&a[i]);
	for(int i=1;i<=n;i++){\/\/..*  i包含结尾休息日 	
		w[i]=w[i-1]+a[i\/2];
	}
	memset(f,-32,sizeof(f));
	f[0]=0;
	for(int i=1;i<=n;i++){\/\/物品 
		for(int j=i;j<=n;j++){\/\/背包体积
		 	f[j]=max(f[j],f[j-i]+w[i]) ;
			
		}
	}
	cout<<f[n]<<endl;	
} 

[ABC256G] Black and White Stones-环形-矩阵优化DP

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2024-01-05 09:54:31

  • 题目大意: 有一个 $n$ 边形,每条边上有 $d+1$个石子,相邻两边公用一个石子。石子有白色或者黑色。问一共有多少种方案使得所有边上的白色石子数量相同。对 998244353 取模。

$n≤10^{12},d≤10^4$。

  • 题目分析 独立思考完成。

每条边的白点数一致。
算上重复的端点,每条边为D+1个数,里面有0...D+1个黑点。黑白是对称的,实际可算一半。
设白点数为t
要考虑端点的情况:
$a00=c(d-1,t-2) $
$a01=a10=c(d-1,t-1) $
$a11=c(d-1,t)$
然后把这n条边连起来。n这么大,基本要o(1)或者 矩阵快速幂转移
设把i条边连起来的方案数为f[i][0/1]
设1号边起点。f[i][0/1]代表连接了第i条边后的右端点颜色。
$fi0=fi-1,0a00+fi-1,1a10 \space \space \space a00 a10 fi-1,0$
$fi1=fi-1,0a01+fi-1,1a11 \space \space \space a01,a11 fi-1,1$

然后通过矩阵快速幂求出$fn0+fn1$  记作f(t) 
	最后的答案:sigma(ft)(t=0..n)

[ABC300E] Dice Product 3-概率期望DP

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2024-01-05 16:29:47

  • 题目大意 给定一个数1,不断乘上骰子的点数,问恰好为$n$的概率是多少?

  • 题目分析

1出现与否没有影响,因此2 3 4 5 6共5种可能,每个数出现的概率为1/5. 也可以通过等比数列推导出。 或者通过递推得到 f(x)=1/6(f(x)+f(x/2)+f(x/3)+f(x/4)+f(x/5)+f(x/6)) f(x)=1/5(f(x/2)+f(x/3)+f(x/4)+f(x/5)+f(x/6))

方法一:利用排列组合 唯一分解定理后,处理2,3,是合起来得到4,6,还是分着得到. 例如样例:300

$2*2*3*5*5   5!\/(2!2!)$  

$4*3*5*5   4!\/2!$

$2*6*5*5 4!\/2!$

方法二: 用前面的递推公式,n太大,记忆化搜索,利用map完成记忆化

方法一代码

#include <bits\/stdc++.h>
using namespace std;
#define int long long
const int mod = 998244353;
const int MAXV = 200000;
int inv[MAXV + 10], jc[MAXV + 10], invjc[MAXV + 10];
int ksm(int a, int b, int res = 1) {
	for (; b; a = a * a % mod, b >>= 1)
		if (b & 1) res = res * a % mod;
	return res;
}
void init() {
	jc[0] = 1;
	for (int i = 1; i <= MAXV; i++)
		jc[i] = jc[i - 1] * i % mod;
	invjc[MAXV] = ksm(jc[MAXV], mod - 2);
	for (int i = MAXV; i > 0; i--)
		invjc[i - 1] = invjc[i] * i % mod;
	for (int i = 1; i <= MAXV; i++)
		inv[i] = jc[i - 1] * invjc[i] % mod;
}
int C(int x, int y) {
	return jc[x] * invjc[y] % mod * invjc[x - y] % mod;
}
signed main() {
	init();
	int n, cnt2 = 0, cnt3 = 0, cnt5 = 0;
	scanf("%lld", &n);
	while (n % 2 == 0) cnt2++, n \/= 2;
	while (n % 3 == 0) cnt3++, n \/= 3;
	while (n % 5 == 0) cnt5++, n \/= 5;
	if (n > 1) {
		puts("0");
		return 0;
	}
	int ans = 0,res;
	for (int i = 0; i <= cnt2; i++) {

		for (int j = 0; j <= cnt3; j++) {
			int cnt6 = cnt3 - j;
			if (cnt2 - i - cnt6 < 0 || (cnt2 - i - cnt6) % 2)
				continue;
			int cnt4 = (cnt2 - i - cnt6) \/ 2;
			int all = i + j + cnt4 + cnt5 + cnt6;
			int sum = ksm(5, all);
			res = C(all, i);
			res = res * C(all - i, j) % mod;
			res = res * C(all - i - j, cnt4) % mod;
			res = res * C(all - i - j - cnt4, cnt5) % mod;
			res = res * C(all - i - j - cnt4 - cnt5, cnt6) % mod;
			(ans += res * ksm(sum, mod - 2) % mod) %= mod;
		}
	   \/\/printf("%lld\n", res);
	    }
	printf("%lld\n", ans);

	return 0;
}

方法二:

#include <bits\/stdc++.h>
using namespace std;
#define int long long
const int mod = 998244353;
const int MAXV = 200000;
int n,inv5;
int ksm(int a, int b, int res = 1) {
	for (; b; a = a * a % mod, b >>= 1)
		if (b & 1) res = res * a % mod;
	return res;
}
map<int,int> dp;
map<int,bool> v;
int dfs(int x)
{
	if(x<1) return 0;
	if(x==1) return 1;
	if(v[x]==true) return dp[x];
	v[x] = true;

	for(int i=2;i<=6;i++) {
	 if(x%i==0)  dp[x]=(dp[x]+dfs(x\/i))%mod ;
	}
	dp[x] = (dp[x]*inv5)%mod;
	return dp[x];
}
signed main() {
	inv5=ksm(5,mod-2);
	scanf("%lld", &n);
	printf("%lld\n",dfs(n));
	return 0;
}

p1433吃奶酪-状态压缩DP

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2024-01-07 08:05:47

  • 题目大意: 从0点出发,不重复走过n个点的最小路径。

  • 小数据,暴力或状态压缩

  • 方法一: 暴力dfs+最优化剪枝

#include<cstdio>
#include<cmath>
#include<algorithm>
using namespace std;
int n;
double dis[16][16],ans=1000000000.0,now,x[16],y[16];\/\/两点距离,答案,现在的距离,每个点的坐标
bool vis[16];\/\/奶酪是否被吃
void dfs(int pos,int num,double now) {
	if(now>ans)\/\/如果现在的距离比答案大就返回
		return;
	if(num==n) {
		if(ans>now)\/\/更新答案
			ans=now;
		return;
	}
	vis[pos]=true;
	for(int i=1; i<=n; i++) {
		if(vis[i]==false) { \/\/枚举每个没有被吃的奶酪
			dfs(i,num+1,now+dis[pos][i]);
		}
	}
	vis[pos]=false;\/\/回溯
}
int main() {
	scanf("%d",&n);
	for(int i=1; i<=n; i++)
		scanf("%lf%lf",&x[i],&y[i]);
	x[0]=0;
	y[0]=0;
	for(int i=0; i<=n; i++) \/\/预处理两点距离
		for(int j=0; j<=n; j++)
			dis[i][j]=sqrt((x[i]-x[j])*(x[i]-x[j])+(y[i]-y[j])*(y[i]-y[j]));
	dfs(0,0,0);
	printf("%0.2f",ans);
}

方法二:状压dp,填表法

#include<bits\/stdc++.h>
using namespace std;

struct F{
	double x, y;
} a[20];

double dist(F a, F b)
{
	return sqrt((a.x-b.x)*(a.x-b.x) + (a.y-b.y)*(a.y-b.y));
}

double f[20][66000];
double dis[20][20];

int n;

int main()
{
	a[0].x = 0;
	a[0].y = 0;
	memset(f, 127, sizeof(f));
	\/\/ cout<<f[0][0]<<endl;
	cin >> n;
	for(int i=1;i<=n;i++)
	{
		cin >> a[i].x >> a[i].y;	
	}	
	for(int i=0;i<=n;i++)
	{
		for(int j=0;j<=n;j++)
		{
			dis[i][j] = dist(a[i], a[j]);
		}
	}
	dis[0][0]=0; 
	for(int i=1;i<=n;i++)
	{
		f[i][(1<<(i-1))] = dis[0][i];
	}
	int End=(1<<n)-1; 
	for(int i=1;i<=End;i++)
	{
		for(int j=1;j<=n;j++)
		{
			
			for(int k=1;k<=n;k++)\/\/ 到了k点了 
			{
				if(j == k){continue;}\/\/j 从k点到j点 
				if((i&(1<<(j-1)))&&(i & (1<<(k-1)))){
					f[j][i] = min(f[j][i], f[k][i-(1<<(j-1))]+dis[k][j]);
				} 
				
				
			}
		} 
	}
	double ans = 1e20;
	for(int i=1;i<=n;i++)
	{
		ans = min(ans, f[i][((1<<n)-1)]);
	}
	printf("%.2lf",ans);
	return 0;	
} 
  • 方法三,状态压缩,刷表
#include<bits\/stdc++.h>
using namespace std;

struct F{
	double x, y;
} a[20];

double dist(F a, F b)
{
	return sqrt((a.x-b.x)*(a.x-b.x) + (a.y-b.y)*(a.y-b.y));
}

double f[20][66000];
double dis[20][20];

int n;

int main()
{
	a[0].x = 0;
	a[0].y = 0;
	memset(f, 127, sizeof(f));
	\/\/ cout<<f[0][0]<<endl;
	cin >> n;
	for(int i=1;i<=n;i++)
	{
		cin >> a[i].x >> a[i].y;	
	}	
	for(int i=0;i<=n;i++)
	{
		for(int j=0;j<=n;j++)
		{
			dis[i][j] = dist(a[i], a[j]);
		\/\/	dis[j][i] = dis[i][j];
		\/\/	cout<<dis[i][j]<<" "; 
		}
	\/\/	cout<<endl;
	}
	dis[0][0]=0; 
	f[0][1]=0;
	int End=(1<<(n+1))-1; 
	for(int i=1;i<=End;i++)
	{
		for(int j=0;j<=n;j++)\/\/到了j点了 
		{
			
			for(int k=1;k<=n;k++)\/\/ 
			{
				if(j == k){continue;}\/\/j 从j点到k点 
				if((i&(1<<(j)))&&(i&(1<<(k)))==0){
					f[k][i|(1<<(k))] = min(f[k][i|(1<<(k))], f[j][i]+dis[j][k]);
					int tmp=i|(1<<(k));
				} 
				
				
			}
		} 
	}
	double ans = 1e20;
	for(int i=1;i<=n;i++)
	{
		ans = min(ans, f[i][((1<<(n+1))-1)]);
	}
	printf("%.2lf",ans);
	return 0;	
} 
  • 方法四:状态压缩,记忆化写法。
#include <bits\/stdc++.h>

using namespace std;

using namespace std;
const int INF = 1e9;
int n;
double x[15];
double y[15];
double dp[15][1<<15];

inline double dist( double x1 , double y1 , double x2 , double y2 ) {
	return sqrt( (x1-x2)*(x1-x2) + (y1-y2)*(y1-y2) );
}
double dfs( int now , int S ) {\/\/s状态下,在哪个点上 
	if( dp[now][S] != -1 ) return dp[now][S];
	dp[now][S] = INF;
	for( int i = 0 ; i < n ; ++i ) {
		if( i == now ) continue;
		if( (S&(1<<i))==0 ) continue;\/\/从i点走到now点。 
		dfs( i , S-(1<<now) );\/\/这个点由哪个点转移而来 
		dp[now][S] = min( dp[now][S] , dp[i][S-(1<<now)] + dist( x[i] , y[i] , x[now] , y[now] ) );
	}
	return dp[now][S];
}
int main() {
	cin >> n;
	for( int i = 0 ; i < n ; ++i ) cin >> x[i] >> y[i];
	for( int i = 0 ; i < n ; ++i ) {
		for( int j = 1 ; j < (1<<n) ; ++j ) {
			dp[i][j] = -1;
		}
	}
	for( int i = 0 ; i < n ; ++i ) dp[i][1<<i] = dist(0,0,x[i],y[i]);
		double mn = INF;
	for( int i = 0 ; i < n ; ++i ) mn=min(mn,dfs( i , (1<<n)-1 ));

	printf( "%.2lf" , mn );
	return 0;
}
共 90 篇博客