- Atcoder訓練
- Harlequin
思維題博弈論,思考每一次怎麼轉化最優,存在兩個答案說明f可以贏,打表發現當所有數字都是偶數時,答案為second,否則為first
- Harlequin
#include <bits/stdc++.h>
using namespace std;
using ll=long long;
int main(){
ll n;
cin>>n;
ll ans=0;
vector<ll>v(n+1);
for(int i=1;i<=n;i++){
ll b;
cin>>b;
if(b%2==0)ans++;
}
if(ans==n)cout<<"second";
else
cout<<"first";
}
- Gathering Children
思維,模擬打表,最終會聚集在LR兩個點上,我們考慮最終要不要交換兩個點,打表模擬情況
最後會發現如果:R+L為偶數,不變,如果R為奇數,那麼L++,R不變,如果L為奇數,R++,L不變
#include <bits/stdc++.h>
using namespace std;
using ll=long long;
ll res[100001];
int main(){
string s;
cin>>s;
int n=s.length();
vector<ll>v(n+1);
int le=1;
for(int i=0;i<n;i++){
if(s[i]=='R'&&s[i+1]!='R'){
v[i]+=le,le=1;
}else if(s[i]=='R'&&s[i+1]=='R')le++;
else le=1;
}
le=1;
for(int i=n-1;i>=0;i--){
if(s[i]=='L'&&s[i-1]!='L'){
v[i]+=le,le=1;
}else if(s[i]=='L'&&s[i-1]=='L')le++;
else le=1;
}
for(int i=0;i<n;i++)
{
if(v[i]!=0)
{
if((v[i]+v[i+1])%2==0)
{
res[i]=(v[i]+v[i+1])/2;
res[i+1]=res[i];
}
else
{
res[i]=floor((v[i]+v[i+1])/2);
res[i+1]=res[i];
if(v[i]%2==0) res[i+1]++;
else res[i]++;
}
i++;
}
}
for(int i=0;i<n;i++) cout<<res[i]<<" ";
}
- Palindrome Hard Problem
之前沒做出來的題,不難但是我做不出來,每次分出來一個數就是一個迴文串,我們統計多少個數就是答案,我把每行可能m個看出成都是一個了
#include <bits/stdc++.h>
using namespace std;
using ll =long long;
map<char,ll>mp;
int main(){
ll n;
int sum=0;
cin>>n;
for(int i=1;i<=n;i++){
string s;
cin>>s;
int k=s.length();
sum+=k;
}
cout<<sum;
}
- Storybooks
標準字首和+二分,沒做過可以回去再做一下,這道題注意邊界和資料範圍就行
#include <bits/stdc++.h>
using namespace std;
using ll =long long;
ll prefix[10011000];
int main(){
int n,k;
cin>>n>>k;
vector<ll>v(n+1);
vector<ll>d(k+1);
for(int i=1;i<=n;i++)cin>>v[i];
for(int i=1;i<=k;i++)cin>>d[i];
sort(v.begin()+1,v.end());
for(int i=1;i<=n;i++)prefix[i]+=v[i]+prefix[i-1];
for(int i=1;i<=k;i++){
ll l=0,r=n;
while(l<=r){
ll mid=(l+r)/2;
if(prefix[mid]>d[i]){
r=mid-1;
}else
l=mid+1;
}
cout<<r<<' ';
}
}
- 動態規劃專項訓練
1.P1434 [SHOI2002] 滑雪
利用記憶化dfs搜素的方法找到最長距離,好題,學會去用記憶化搜素。
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const int MAXN = 105;
ll v[MAXN][MAXN];
ll dp[MAXN][MAXN];
int n, m;
int dx[4] = {-1, 0, 1, 0};
int dy[4] = {0, 1, 0, -1};
ll dfs(int x, int y) {
//這裡就是記憶化
if (dp[x][y] != 0) return dp[x][y];
dp[x][y] = 1;
for (int i = 0; i < 4; ++i) {
int nx = x + dx[i];
int ny = y + dy[i];
if (nx >= 1 && nx <= n && ny >= 1 && ny <= m && v[nx][ny] < v[x][y]) {
dp[x][y] = max(dp[x][y], dfs(nx, ny) + 1);
}
}
return dp[x][y];
}
int main() {
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
cin >> n >> m;
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= m; ++j) {
cin >> v[i][j];
}
}
ll ans = 0;
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= m; ++j) {
ans = max(ans, dfs(i, j));
}
}
cout << ans << endl;
return 0;
}
2.最長公共子序列
二維dp,狀態轉移方程a[i]=b[i]時,dp[i][j]=dp[i-1][j-1]+1,否則dp[i][j]=max(dp[i-1][j],dp[i][j-1])時間複雜度為n*n太高
最長公共子序列有專門的問題集最長公共子序列LCS問題
dp程式碼,注意邊界dp[0][0]=0;
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const int MAXN = 5001;
ll a[1000010];
ll b[100010];
ll dp[MAXN][MAXN];
int n, m;
int main(){
string s;
string t;
cin>>n;
for(int i=1;i<=n;i++)cin>>a[i];
for(int i=1;i<=n;i++)cin>>b[i];
ll ans=1;
dp[0][0]=0;
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
if(a[i]==b[j]){
dp[i][j]=dp[i-1][j-1]+1;
}else{
dp[i][j]=max(dp[i-1][j],dp[i][j-1]);
}
ans=max(ans,dp[i][j]);
}
}
cout<<ans;
}
對於排列的 LCS 問題,有一種特殊的方法可以線上性時間內解決
我們可以利用排列的性質,將問題轉化為最長遞增子序列(LIS)問題,而 LIS 問題可以在 O(n log n) 時間內解決。
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const int MAXN = 100010;
int a[MAXN];
int b[MAXN];
int pos[MAXN];
int dp[MAXN];
int n;
int main(){
cin >> n;
for(int i = 1; i <= n; i++) cin >> a[i];
for(int i = 1; i <= n; i++) cin >> b[i];
// 將 a 中的元素位置對映到 b 中
for(int i = 1; i <= n; i++) {
pos[b[i]] = i;
}
// 將 a 中的元素替換為在 b 中的位置,然後求 LIS
for(int i = 1; i <= n; i++) {
a[i] = pos[a[i]];
}
// 求 LIS
int len = 0;
for(int i = 1; i <= n; i++) {
int idx = lower_bound(dp, dp + len, a[i]) - dp;
dp[idx] = a[i];
if(idx == len) len++;
}
cout << len << endl;
return 0;
}
揹包問題
3.NOIP2001 裝箱問題
01簡化揹包的基本過程,怎麼去選取,還有最佳化記憶體大小的01滾動,就地滾動,轉移方程dp[i%2][j]=dp[(i-1)%2][j]||dp[(i-1)%2][j-a[i]]
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const int MAXN = 100010;
int pos[MAXN];
int dp[2][20020];
int a[40];
int n,m;
// 01揹包 01滾動
int main(){
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
int v,n;
cin>>v>>n;
for(int i=1;i<=n;i++)cin>>a[i];
dp[0][0]=1;
/*
01滾動
for(int i=1;i<=n;i++){
for(int j=0;j<=v;j++){
//解釋:還有空位放的話會放自己的值加上前面選的值加上當前的值,否則只放當前的值
//注意j不能從a[i]開始而是j從0開始,原因是小於a[i]的數也要留到下一輪
if(j>=a[i]){
dp[i%2][j]=dp[(i-1)%2][j]||dp[(i-1)%2][j-a[i]];
}else
dp[i%2][j]=dp[(i-1)%2][j];
}
}
*/
//就地滾動
for(int i=1;i<=n;i++){
for(int j=v;j>=a[i];j--)
dp[j]=dp[j]||dp[j-a[i]];
}
ll ans=0;
for(int i=v;i>=0;i--){
if(dp[i]==1){
ans=i;
break;
}
}
cout<<ans;
}
2.【模板】01揹包
現在目前有兩種問法,一種是揹包能裝的最大價值,另一種是剛好裝滿的情況下,揹包的最大價值
第一張解決方法的轉移方程和第二種是一樣的dp[j]=max(dp[j].dp[j-a[i]]+w[i])算出來不一定剛剛好容量大價值最大,但是一定在裡面;
第二種是初始化為負無窮,最終判斷pd[v]有沒有初始化,有的話剛剛好滿足條件的答案就是pd[v],都用01滾動
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1010;
int f[N]; // f[j]表示體積為j的情況下的總價值
int v[N], w[N]; // 物品的體積 價值
int main()
{
int n, m;
cin >> n >> m;
for(int i = 1; i <= n; i ++) cin >> v[i] >> w[i];
memset(f, -0x3f, sizeof f); //一開始都初始化為負無窮 方便記錄是否有恰好體積為j的情況出現過
f[0] = 0; // 最開始體積為0價值為0
for(int i = 1; i <= n; i ++)
for(int j = m; j >= v[i]; j --) // j>=v[i] 保證了可以選擇第i個物品
{
f[j] = max(f[j], f[j - v[i]] + w[i]); // 這裡其實消去了一維
// 原式為f[i][j] = max(f[i][j], f[i - 1][j - v[i]] + w[i]);
// 為了防止計算時所需要的上一層的數值被覆蓋所以倒序遍歷這樣算f[j]時用到的f[j - v[i]]就還是和原來一樣
}
int ans1 = 0, ans2 = 0;
for(int i = 0; i <= m; i ++) ans1 = max(ans1, f[i]); // 找到最大值
if(f[m] < 0) ans2 = 0; // 如果f[m]<0說明沒被初始化過 沒有體積恰好為m的情況出現
else ans2 = f[m]; //否則根據定義可知 f[m]的值就是揹包恰好裝滿的情況下的最大值
cout << ans1 << endl;
cout << ans2 << endl;
}
- 牛客萌新聯賽(第一場)(覆盤)
1.造數
逆向思維,怎麼把n變成0,能除2,就除,不能就減
#include<bits/stdc++.h>
using namespace std;
using ll =long long;
ll n,m,k,t;
ll v[1000100];
bool is(ll n)
{
return n > 0 && (n & -n) == n;
}
int main(){
cin>>n;
if(n==0)cout<<0;
else if(n==1)cout<<1;
else if(n==2)cout<<1;
else if(n==3)cout<<2;
else if(n==4)cout<<2;
else{
ll ans=1;
if(is(n)){
while(n>2){
n/=2;
ans++;
}
}else{
ll ans = 0;
while (n >= 2) {
if (n % 2 == 0) {
n /= 2;
} else {
n -= 1;
}
ans++;
}
cout<<ans;
}
}
return 0;
}
2.愛探險的朵拉
很好的記憶化搜素,我們需要認認真真學,這個每個路線判斷是否已經走過,或者怎麼避免已經走過的路線
#include <bits/stdc++.h>
using namespace std;
const int N = 2e5+5;
vector<int> v(100010);
vector<int> b(100010);
int ma=0;
int cnt=1;
void bfs(int x,int l){
//每次標記走過的
b[x]=cnt;
//直接找最大
ma=max(ma,l);
//判斷是否走過,這裡不需要擔心,不會走重複,之前走過的已經標記
if(b[v[x]]!=cnt){
bfs(v[x],l+1);
}
}
int main(){
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
int n;
cin>>n;
for(int i=1;i<=n;i++)cin>>v[i];
for(int i=1;i<=n;i++){
//這裡就是原因
if(b[i]==0){
//cnt不同,路線不同
cnt++;
bfs(i,1);
}
}
cout<<ma;
}
3.旅途的終點
思維+二分,二分答案x一定能走到的國家數,是按順序走的,然後每次二分檢查的時候排序前x個國家數,前面的數我們不用神力,如果滿足條件就l=mid+1.
這種按順序的二分和思維一定要掌握
#include <bits/stdc++.h>
using namespace std;
using ll =long long;
vector<ll>v(2e5+8);
ll n,m,k;
bool check(ll x){
ll hp=m;
vector<ll>s(n+1);
s=v;
sort(s.begin()+1,s.begin()+1+x,greater<ll>());
for(int i=k+1;i<=x;i++){
if(hp<=s[i])return false;
hp-=s[i];
}
return true;
}
int main(){
cin>>n>>m>>k;
ll ans=0;
for(int i=1;i<=n;i++)cin>>v[i];
ll l=0,r=n;
while(l<=r){
ll mid=(l+r)/2;
if(check(mid)){
l=mid+1;
ans=mid;
}else
r=mid-1;
}
cout<<ans;
}