图论数据结构dp
codeforces 1900上下,简单的1600
A - DZY Loves Sequences
题意:
给你一个序列,让你找一个子序列,使这个子序列中的数最多修改一次,这个子序列为最长连续上升子序列。这里的子序列要求是连续的
思路:
一眼dp问题是,阶段是从1到n有两个状态到i位置为结尾,修改一次的最长上升子序列是的长度,和i为结尾,修改0次最长上升子序列的长度。明显,如果a[i]>a[i-1],有转移方程
d
p
[
i
]
[
j
]
=
d
p
[
i
−
1
]
[
j
]
+
1
dp[i][j] = dp[i-1][j]+1
dp[i][j]=dp[i−1][j]+1,如果不成立,我们很容易想到,
d
p
[
i
]
[
1
]
=
d
p
[
i
]
[
0
]
+
1
dp[i][1] = dp[i][0] + 1
dp[i][1]=dp[i][0]+1 ,并且
d
p
[
i
]
[
0
]
=
1
dp[i][0] =1
dp[i][0]=1但是我们忽略了转移的条件,如果序列为3,2,3其实我们是转移不过来的,我们需要对比
a
[
i
]
a[i]
a[i]和
a
[
i
−
2
]
a[i-2]
a[i−2]位置的大小
AC代码:
代码写的很烂,不轻易看这一版
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e5+5;
int a[maxn];
int dp[maxn][2];
int main(){
int n;
scanf("%d",&n);
for(int i = 1 ;i <= n;i++){
scanf("%d",&a[i]);
// dp[i][0] = dp[i][1] = 1;
}
int ans = 1;
int mx = 0,mx2 = 0;
for(int i = 1 ;i <= n;i++){
if(a[i]>mx){
dp[i][0] = dp[i-1][0] + 1;
mx = a[i];
}
else {
dp[i][0] = 1;
mx = a[i];
dp[i][1] = dp[i-1][0] + 1;
if(i<2)mx2 = a[i-1] + 1;
if(a[i]>a[i-2]+1)mx2 = a[i];
else mx2 = a[i-1] + 1;
}
if(a[i]>mx2){
dp[i][1] = dp[i-1][1] + 1;
mx2 = a[i];
}
if(dp[i][1] == 0){
mx2 = 0;
}
// else {
// dp[i][1] = 1;
// mx2 = a[i];
// }
ans = max(ans,dp[i][0]+1);
ans = max(ans,dp[i][1]);
}
if(ans>n)ans = n;
cout<<ans<<endl;
}
B - Modulo Sum
题意:
给你一个序列,和一个m,问是否存在从这个序列中选出一些数,随意选,但是不能重复,使这些数的和可以被m整除
思路:
一看序列a 的数据范围很大,然后这个m的范围1000,先让a序列取模m,取模后的结果均小于m,并且对于实际结果没有影响。然后就是在开始我的想法是这个样的,结果均小于m,那么如果我就可以等价于从这些数中找到一些数,让这些数之和等于m了吗,这是一个经典的背包问题,数不重复使用就是0/1背包,然后我们外层循环i表示使用到的数都在i位置之前,内层循环表示和为j然后dp数组压缩到一维,倒序循环表示0/1背包。但是,我又发现,就算我可以频繁取模,但是我的和可以超过m加入8,8两个数,m=11,我使用8,8可以组合成16,16和
16
m
o
d
11
=
5
16 mod 11=5
16mod11=5等价所以我的dp[5]应该置1,所以我很容易就想到了
d
p
[
j
m
o
d
m
]
∣
=
d
p
[
j
−
a
[
i
]
]
dp[j mod m ] |= dp[j-a[i]]
dp[jmodm]∣=dp[j−a[i]]把j的范围扩大到2m,但是wa了,于是我又发现,因为我们是从后往前转移,如果取模就很破坏这个从后往前转移的条件,因为我转移16,就会转移到5上,我这次就会多算一个5,于是我就先不去模,在转移完成之后,在手动将m-2m,范围能转移到的数据在拷一份到0-m即可
AC代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1e6+6;
ll a[maxn];
ll dp[maxn];
int main(){
int n,m;
cin>>n>>m;
for(int i = 1;i <= n;i++){
scanf("%lld",&a[i]);
a[i] %= m;
if(a[i]==0){
printf("YES\n");
return 0;
}
}
dp[0] = 1;
for(int i = 1;i <= n;i++){
for(int j = m*2;j >= a[i];j--){
// dp[j%(m+1)] |= dp[(j-a[i])];
if(j>m){
dp[j] |= dp[(j-a[i])];
// cout<<j%m<<" "<<dp[j%m]<<" "<<j-a[i]<<endl;
}
else dp[j] |= dp[(j-a[i])];
// cout<<j<<" "<<j%m<<" "<<dp[j]<<" "<<dp[j%m]<<" "<<a[i]<<endl;
}
for(int j = 1;j <= m; j++)dp[j] |= dp[j+m];
// for(int i = 1;i<=m;i++){
// cout<<dp[i]<<" ";
// }
// cout<<endl;
}
if(dp[m]==1){
printf("YES\n");
}
else printf("NO\n");
}
Rescue Nibel!
题意:有一个数组,给你一堆 l , r l,r l,r,然后让你在l到r时间内这个灯会亮,要求选取k个灯,要求这k个灯有可以同时量,问能选出多少种k个灯的组合
思路:
思维我哭了,思维题有做不出来,崩溃中,就是我一直在纠结,当某一个时刻的亮灯数大于k就可以在这个时刻选取k个灯,就是一个组合数的问题,但是!会有重复,比如1时刻有四个灯亮,但是下一个时刻还是有四个灯亮,我们不知道这两个时刻的灯是否是同一些灯。
实际上,我们我们记录某一位置开始的时刻的亮灯情况,然后就是我们选的集合必定包括该时刻开始亮的灯,这样就不会重复,即某位置
C
(
s
u
m
,
k
)
−
C
(
s
u
m
−
s
t
a
r
t
,
k
)
C(sum,k) - C(sum-start,k)
C(sum,k)−C(sum−start,k),然后差分维护区间修改,加离散化,组合数学的板子
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 3e5+5;
const int Maxn = maxn;
const int mod = 998244353;
int a[maxn<<1];
int c[maxn<<1];
struct Ques{
int u,v;
}qu[maxn];
int st[maxn<<1];
//预处理阶乘表 fac[n]=(fac[n-1]*n)%p=(n!)%p
ll fac[Maxn+10];
void setFac(int n){
fac[0]=1;
for(int i=1;i<=n;i++){
fac[i]=1LL*fac[i-1]*i%mod;
}
}
ll binaryPow(ll a,ll b,ll m){
ll ans = 1;
while(b){
if(b & 1){
ans = ans * a % m;
}
a = a * a % m;
b >>= 1;
}
return ans;
}
//计算组合数 $C_{n}^{m}$
ll C(int n,int m){
if(n<m) return 0;
if(n<0||m<0) return 0;
ll t=fac[n-m]*fac[m]%mod;
ll inv=binaryPow(t,mod-2,mod);
return fac[n]*inv%mod;
}
int main(){
int n,k;
cin>>n>>k;
setFac(n);
int cnt = 0;
for(int i = 1;i<=n;i++){
scanf("%d%d",&qu[i].u,&qu[i].v);
c[++cnt] = qu[i].u;
c[++cnt] = qu[i].v;
}
sort(c+1,c+cnt+1);
int nn = unique(c+1,c+cnt+1) - (c+1);
for(int i = 1;i<=n;i++){
qu[i].u = lower_bound(c+1,c+nn,qu[i].u) - c;
qu[i].v = lower_bound(c+1,c+nn,qu[i].v) - c;
}
for(int i = 1;i <= n;i++){
int u = qu[i].u;
int v = qu[i].v;
a[u]++;
a[v+1]--;
st[u]++;
}
int sum = 0;
ll ans = 0;
for(int i = 1;i <= 6e5+1;i++){
sum += a[i];
if(sum>=k){
ans = ((ans%mod + C(sum,k)%mod)%mod - C(sum-st[i],k)%mod+mod)%mod;
}
// cout<<ans<<endl;
}
cout<<ans<<endl;
}
Coffee Break
题意:大概就是给定一天的休息时间,然后一天的总时间,问最少多少天才能把这些休息时间都使用完,使得两次休息时间间隔大于d
思路:
有点想贪心,就是排个序,如果我在第i个休息时间,发现i-d之前还是有没有没有休息的时间,我就可以让i和i-d在一天内完成休息,加入到并查集中去即可。在处理一下细节
AC代码
#include <bits/stdc++.h>
using namespace std;
const int maxn = 2e5+5;
struct A{
int id,v;
}a[maxn];
bool cmp(A x,A y){
return x.v<y.v;
}
bool cmp2(A x,A y){
return x.id<y.id;
}
int fa[maxn];
int get(int x){
if(fa[x]==x)return x;
return fa[x] = get(fa[x]);
}
void merge(int u,int v){
int p = get(u);
int q = get(v);
if(p!=q){
fa[p] = q;
}
}
int num[maxn];
int main(){
int n,m,d;
scanf("%d%d%d",&n,&m,&d);
for(int i = 1 ;i<=n;i++){
fa[i] = i;
scanf("%d",&a[i].v);
a[i].id=i;
}
sort(a+1,a+n+1,cmp);
int p=1;
for(int i = 1;i <= n;i++){
if(a[i].v-d>a[p].v){
merge(a[i].id,a[p].id);
p++;
}
}
int cnt = 0;
for(int i = 1;i<=n;i++){
if(fa[a[i].id]==a[i].id){
cnt++;
num[a[i].id] = cnt;
}
}
sort(a+1,a+n+1,cmp2);
cout<<cnt<<endl;
for(int i = 1;i<=n;i++){
printf("%d ",num[get(i)]);
}
}
F - TediousLee
题意:给你一棵数生成的规律,给你一个染色的策略,问最多染多少个节点
思路:经典简单题不会做,其实就是一个递推解法,发现一颗高度为h的数是由四部分构成,一个根节点,一个高度为h-1的树,两个高度为h-2的树,转移方程 a n s [ i ] − a n s [ i − 1 ] + a n s [ i − 2 ] ∗ 2 ans[i] - ans[i-1] + ans[i-2]*2 ans[i]−ans[i−1]+ans[i−2]∗2,另外就是判断该树的根节点是否被染色,如果子树的根节点全部没有被染色,该树方案数在加1,并且标记该树根节点被染色。
AC代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int mod = 1e9+7;
const int maxn = 2e6+6;
ll a[maxn];
int rt[maxn];
int main(){
int t;
scanf("%d",&t);
a[1] = 0;
a[2] = 0;
a[3] = 1;
rt[3] = 1;
a[4] = 1;
rt[4] = 0;
for(int i = 5;i<=2e6;i++){
a[i] = (a[i-1] + a[i-2]*2)%mod;
if(rt[i-1]==0&&rt[i-2]==0){
rt[i] = 1;
a[i]++;
}
}
while(t--){
int n;
scanf("%d",&n);
printf("%lld\n",a[n]*4%mod);
}
}
Carrying Conundrum
题意:给一个错误的加法模式,给出一个和,问有多少种加数可以按错误的模式相加,得到这个和
思路,看他的错误模式,将原数拆分奇数位置,偶数位置,然后拆成两个数,然后就都是正常的模式了,因为加数不能有0,所以需要在减去0 的情况
AC代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int main(){
int t;
scanf("%d",&t);
while(t--){
int n;
scanf("%d",&n);
ll odd = 0;
ll even = 0;
int len = 1,len2 = 1;
int l = 0;
while(n){
if(l%2==0){
odd += (n%10)*len;
len*=10;
}
else {
even += (n%10)*len2;
len2*=10;
}
l++;
n/=10;
}
if(odd==0||even==0){
printf("%lld\n",max(odd,even)-1);
}
else {
printf("%lld\n",(even+1)*(odd+1)-2);
}
}
}
Pashmak and Parmida’s problem
题意:给你一个f函数然后给要求
思路:预处理处一个位置算i的f函数的值和j的f函数的值,转化为两个数组逆序对的问题,在安树状数组处理即可
#include <bits/stdc++.h>
#define INF 0x3f3f3f3f
#define lowbit(x) (x&(-x))
using namespace std;
typedef long long ll;
const int maxn = 1e6+5;
int a[maxn];
map<int ,int>tol;
map<int,int>bef;
int pre[maxn];
int pos[maxn];
int c[maxn];
int n;
void add(int x,int v){
while(x<=n){
c[x] += v;
x+=lowbit(x);
}
}
int ask(int x){
int ans = 0;
while(x){
ans += c[x];
x -= lowbit(x);
}
return ans;
}
int main(){
scanf("%d",&n);
for(int i = 1;i <= n;i++){
scanf("%d",&a[i]);
tol[a[i]] += 1;
}
for(int i = 1;i <= n;i++){
bef[a[i]] += 1;
pre[i] = bef[a[i]];
pos[i] = tol[a[i]]-bef[a[i]]+1;
// cout<<i<<":"<<pre[i]<<" "<<pos[i]<<endl;
}
ll ans = 0;
for(int i = n;i>0;i--){
ans += ask(pre[i]-1);
add(pos[i],1);
}
cout<<ans<<endl;
}
计算无向图中的最小环
如果出现环套环,这种情况
dfs标记深度,判环是不可取的,并查集维护集合大小也不可续,dfs搜索到自身也不可取
我么给出了一种复杂度比较大的方法
Floyd方法:
众所周知,Floyd算法求最短路的过程是三重循环。
当最外层恰好循环到
k
k
k 时,代表着目前所求出的最短路所含的点集为
[
1
,
k
]
[1,k]
[1,k]
在第k次循环时
d
p
[
i
]
[
j
]
dp[i][j]
dp[i][j]是i到j的最短路,并且不经过k,我们看k这个点,他经过了两个点,然后这两个点的最短路是
d
p
[
i
]
[
j
]
dp[i][j]
dp[i][j],那说明经过至少有k,i,j三个点的最小环就可以求出来了,
注意初始化dp数组的值
例题:
- Shortest Cycle
首先我们看出来了,应该是抽屉原理(n个抽屉,如果我们放n+1个小球,必定会有两个小球在一个抽屉),一共就位,如果一位存在三个数都为1,那么答案就是3,也就是说如果给定的数个数大于64*2,那么必行会有某一位存在三个1,
那么也就是把问题的规模放到的100左右,然后就是刚好可以利用floyd算法求一个最小环了
#include <bits/stdc++.h>
#define INF 0x3f3f3f3f
using namespace std;
typedef long long ll;
const int maxn = 1e5+5;
ll a[maxn];
ll g[200][200];
ll dp[200][200];
ll ans = INF;
int main(){
int n;
int cnt = 0;
scanf("%d",&n);
for(int i = 0;i<n;i++){
ll x;scanf("%lld",&x);
if(x!=0)a[cnt++]=x;
}
if(cnt>64*2){
printf("3\n");
return 0;
}
for(int i =0;i < cnt;i++){
for(int j = 0 ;j<cnt;j++){
dp[i][j] = 100;
g[i][j] = 100;
}
}
for(int i = 0;i<cnt;i++){
dp[i][i] = 0;
for(int j = 0;j < i;j++){
if((a[i]&a[j])!=0){
g[i][j] = g[j][i] = 1;
dp[i][j] = dp[j][i] = 1;
}
}
}
for(int k = 0;k < cnt;k++){
for(int i = 0;i < k; i++){
for(int j = i+1;j<cnt;j++){
ans = min(ans,dp[i][j] + g[i][k] + g[k][j]);
}
}
for(int i=0;i<cnt;i++){
for(int j = 0;j<cnt;j++){
dp[i][j] = min(dp[i][j],dp[i][k] + dp[k][j]);
}
}
}
if(ans>=100){
cout<<-1<<endl;
}
else cout<<ans<<endl;
}