HGOI 20191103am 题解
Problem A number
使用一个$2^k$数集中每个元素的和表示数$n$,不同集合的数目有多少?
对于$100\%$的数据满足$1 \leq n \leq 10^6$
Solution :
$f[i][j]$表示使用不大于$2^i$的数组成集合,构成数$j$的不同集合数目。
利用完全背包的思想,一开始$f[i+1][j] = f[i][j] + \sum_{k} f[i][j-k * 2^{i+1}]$
显然可以通过滚动数组来优化,利用完全背包的思想,我们得到如下算法:
一开始令$f[i+1][j] = f[i][j]$,然后从小到大考虑每一个$j$,用$f[i+1][j] += f[i+1][j-2^{i+1}]$的dp方程转移。
时间复杂度$O(n log_2 n)$
# include<bits/stdc++.h> # define int long long # define MOD(x) ((x>=mo)?(x-mo):(x)) using namespace std; const int N=1e6+10; const int mo=1000000007; int n,f[N]; signed main() { scanf("%lld",&n); f[0]=1; for (int i=0;(1<<i)<=n;i++) for (int j=0;j<=n;j++) if (j>=(1<<i)) f[j]=MOD(f[j]+f[j-(1<<i)]); printf("%lld\n",f[n]%mo); return 0; }
Problem B game
有$n$个变量$x_1,x_2,...,x_n$,并给出$d$, 现在有$m$个限制$(a,b)$,满足$|x_a - x_b| \leq d$
求出$max\{x_1,x_2,...,x_n\} - min\{x_1,x_2,...,x_n\}$的最大值,且满足上述限制。
对于$100\%$的数据满足$n\leq 500$
Solution :
首先答案一定是$d$的倍数,所以我们只需要将每一条边权变为$1$,来考虑这个问题。
将每一条限制建图,我们不妨考虑从点$i$开始到点$i$结束的一个环。
显然,要让这个环上的点都满足条件,其权值最大的点必然是这个环的中点。
这个中点的权值,等价于从$i$为源点,到所有目标点$j$最短路的最大值。
考虑不在这个环上的所有边,必然可以构造出一种合法状态使整个图满足条件。
所以,本题的答案和求出这个图中最短路的最大值等价。
直接用$floyd$即可,时间复杂度为$O(n^3)$
#include<bits/stdc++.h> using namespace std; int n; int f[55][55]; int d; int solve(){ cin >> n; cin >> d; for (int i=1;i<=n;i++){ for (int j=1;j<=n;j++){ char c; cin >> c; if (c == 'N') f[i][j] = 0x3f3f3f3f; else f[i][j] = 1; if (i == j) f[i][j] = 0; } } for (int k=1;k<=n;k++) for (int i=1;i<=n;i++) for (int j=1;j<=n;j++) f[i][j] = min(f[i][j], f[i][k] + f[k][j]); int ans = 0; for (int i=1;i<=n;i++){ for (int j=1;j<=n;j++) ans = max(ans, f[i][j]); } if (ans > 100000) cout << -1 << endl; else cout << (long long)ans * d << endl; } int main(){ // freopen("bridge.in","r",stdin); // freopen("bridge.out","w",stdout); int T; cin >> T; while (T--){ solve(); } }
Problem C queue
有一个$[1,n]$的排列,每一次可以将一个数移到排列的头部或者尾部。
求出最小操作次数使得排列有序。
对于$100\%$的数据满足$1 \leq n\leq 10^5$
Solution :
本题有一个加强版$CF1223D$. (可以把那个代码粘贴过来,然后就A掉了)
还是重新考虑这个弱化版本,甚至还不需要用离散化。
一个答案的构造一定满足$l,l-1,l-2,...,1$依次放到队首,$r,r+1,...,n$依次放到队尾。
于是我们可以$dp$,设$dp[i]$表示$i$之前的最长的长度,使得$i - dp[i]+1, i-dp[i] , ... i$这些数有序。
那么最后的答案必然是$n - max(dp[i])$
对于本题,等价于求出n - 连续最长上升子序列,时间复杂度为$O(n)$
# include <bits/stdc++.h> using namespace std; const int N=3e5+10; int n,a[N],dp[N],MinID[N],MaxID[N]; vector<int>tmp; inline int read() { int X=0,w=0; char c=0; while(c<'0'||c>'9') {w|=c=='-';c=getchar();} while(c>='0'&&c<='9') X=(X<<3)+(X<<1)+(c^48),c=getchar(); return w?-X:X; } int main() { int T=1; while (T--) { tmp.clear(); n=read(); for (int i=1;i<=n;i++) { a[i]=read(); tmp.push_back(a[i]); } sort(tmp.begin(),tmp.end()); tmp.erase(unique(tmp.begin(),tmp.end()),tmp.end()); for (int i=1;i<=n;i++) { a[i] = lower_bound(tmp.begin(),tmp.end(),a[i]) - tmp.begin() + 1; } int sz = tmp.size(); for (int i=1;i<=sz;i++) MinID[i]=0x3f3f3f3f,MaxID[i]=-0x3f3f3f3f; for (int i=1;i<=n;i++) { MinID[a[i]] = min(MinID[a[i]],i); MaxID[a[i]] = max(MaxID[a[i]],i); } dp[1]=1; int ans = sz-1; for (int i=2;i<=sz;i++) { if (MaxID[i-1] < MinID[i]) dp[i]=dp[i-1]+1; else dp[i]=1; ans = min(ans,sz-dp[i]); } printf("%d\n",ans); } return 0; }
# include<bits/stdc++.h> using namespace std; const int N=1e5+10; int n,p[N],a[N],f[N],ans; int main() { scanf("%d",&n); for (int i=1;i<=n;i++) scanf("%d",&a[i]),p[a[i]]=i; for (int i=1;i<=n;i++) { f[i]=1; if (p[a[i]-1]<i) f[i] = max(f[i],f[p[a[i]-1]]+1); ans = max(ans,f[i]); } printf("%d\n",n-ans); return 0; }