2020牛客国庆集训派对day4

这篇博客涵盖了多种算法问题的解决方案,包括字符串的数字比较、等差数列查找、紧急疏散路径规划、最短非子序列构造、数组排序优化以及颜色树的更新。涉及字符串分割、二叉树的LCA计算、单峰数组构建和全排列还原等概念。
摘要由CSDN通过智能技术生成

A Digits Are Not Just Characters

  • 给出n个字符串si和一个base串s0,问si和s0的大小关系。

比较的时候,先将s分割成数字串和字符串,然后对应去比较。

 

#include <bits/stdc++.h>
using namespace std;
bool isdig(char c){
  return c>='0'&&c<='9';
}

int check1(string s1, string s2){
  int num1 = 0, num2 = 0;
  string str1, str2;
  if(isdig(s1[0])){
    num1 = atoi(s1.c_str());
  }
  else {
    str1= s1;
  }
  if(isdig(s2[0])){
    num2 = atoi(s2.c_str());
  }else str2 = s2;
  if(num1 || num2){
    if(num1==0) return 1;
    if(num2==0) return -1;
    if(num1==num2)return 0;
    return num1<num2?-1:1;
  }
  if(str1==str2)return 0;
  return str1<str2?-1:1;
}

vector<string> parse(string s){
  vector<string> ret;
  string tmp;
  for(int i = 0; i< s.size();++i){
    if(i==0 || (isdig(s[i-1])^isdig(s[i]))){
      if(tmp.size()) ret.push_back(tmp);
      tmp = s[i];
    }
    else {
      tmp += s[i];
    }
  }
  if(tmp.size()) ret.push_back(tmp);
  return ret;
}

bool check(string s0, string s1){
  vector<string> ss1 = parse(s0);
  vector<string> ss2 = parse(s1);
  int n = ss1.size(), m = ss2.size();
  for(int i = 0; i < min(n,m); ++i){
    int ret = check1(ss1[i],ss2[i]);
    if(ret==0)continue;
    return ret<0;
  }
  return n<m;
}

int main(){
  int n;
  cin>>n;
  string s0;
  cin>>s0;
  
  for(int i = 0; i < n; ++i){
    string s;
    cin>>s;
    if(check(s,s0))printf("-\n");
    else printf("+\n");
  }
}

 

B Arithmetic Progressions

  • 求一个最长的等差数列

枚举i,j为等差数列的最后两个,然后dp[i][j] = dp[j][(2*j-i)]+1

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

int dp[5010][5010];
int a[5010];
int main(){
  memset(dp,0,sizeof(dp));
  int n;
  cin>>n;
  map<int,int> pos;
  for(int i = 0; i < n; ++i){
    cin>>a[i];
  }
  
  sort(a,a+n);
  int ans = 2;
  for(int i = 0; i < n; ++i){
    dp[i][i] = 1;
    for(int j = i-1; j>=0;--j){
      dp[i][j] = 2;
      int pre = 2*a[j]-a[i];
      if(pos.find(pre)==pos.end())continue;
      dp[i][j] = max(dp[i][j], dp[j][pos[pre]]+1);
      ans = max(ans, dp[i][j]);
    }
    pos[a[i]]=i;
  }
  cout<<ans<<endl;
}

 

C Emergency Evacuation

  • 给出一个n*m的格子图,然后图上的人都要跑出去,每一个格子不能同时站两个人,问最短时间

由于出口只有一个,因此反过来算从出口进去回到原来位置的最短时间。

按照距离排序,那么每个人的时间就是等待时间加上回去的距离。进去之后前面的肯定不会卡住后面的。

 

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

int main(){
  vector<pair<int,int>> x;
  int r,s,p;
  scanf("%d%d%d",&r,&s,&p);
  for(int i = 0; i < p; ++i){
    int a,b;
    scanf("%d%d",&a,&b);
    if(b>s)b = 2*s +1 - b;
    a = r+1-a;
    x.push_back(make_pair(a,b));
  }
  auto cmp = [&](const pair<int,int> &a, const pair<int,int> &b){
    int dis1 = (s-a.second+1) + a.first;
    int dis2 = (s-b.second+1) + b.first;
    return dis1>dis2;
  };
  sort(x.begin(),x.end(),cmp);
  
  int ans =0;
  for(int i = 0; i < p; ++i){
    int dis = (s-x[i].second+1) + x[i].first;
    ans = max(ans, dis + i);
  }
  cout<<ans<<endl;
}

 

D Shortest Common Non-Subsequence

  • 求一个最短串,不是A,B两个的子串

其实题目就是求一个最短的串,然后能跑满A,B两个串。

dp[i][j]表示在A的第i位B的第j位开始匹配,匹配到最后时的最短距离。

那么有两种选择,去0或者去1.转移就是dp[i][j] = min(dp[next[i][0]][next[j][0]], dp[next[i][1]][next[j][1]])+1

这样就可以求出从00开始的最短串了。然后构造这个最短串,末尾加个0就是答案了。(其实这个0不用加,直接跑到两个串末尾就自动加上去了)

 

#include <stdio.h>
#include <string>
#include <string.h>
#include <iostream>
using namespace std;

int dp[4010][4010];
int nt1[4010][2],nt2[4010][2];
int path[4010][4010];
int len1,len2;

int get_min(int x, int y){
	if(x==-1) return y;
	if(y==-1) return x;
	return min(x,y);
}

string ans;
void get_ans(int i, int j){
	if(i==len1+1&&j==len2+1){
		return;
	}
	ans+= path[i][j] + '0';
	get_ans(nt1[i][path[i][j]],nt2[j][path[i][j]]);
}

int main(){
  string s1,s2;
  cin>>s1>>s2;
  if(s1.size() > s2.size()) swap(s1,s2);

  len1 = s1.size(), len2 = s2.size();
  nt1[len1+1][0] = nt1[len1+1][1] = len1+1;
  nt2[len2+1][0] = nt2[len2+1][1] = len2+1;

  for(int i = len1;i>=0;--i){
    nt1[i][0] = nt1[i+1][0];
    nt1[i][1] = nt1[i+1][1];
    nt1[i][s1[i]-'0'] = i+1;
  }

  for(int i = len2; i>=0;--i){
    nt2[i][0] = nt2[i+1][0];
    nt2[i][1] = nt2[i+1][1];
    nt2[i][s2[i]-'0']=i+1;
  }

  dp[len1+1][len2+1]=0;
  for(int i = len1+1; i>=0;--i){
    for(int j = len2+1; j >=0;--j){
		if(i==len1+1&&j==len2+1)continue;
      int ret1 = dp[nt1[i][0]][nt2[j][0]]+1;
      int ret2 = dp[nt1[i][1]][nt2[j][1]]+1;
      if(ret1<=ret2){
        dp[i][j] = ret1;
        path[i][j] = 0;
      }
      else {
        dp[i][j] = ret2;
        path[i][j] = 1;
      }
    }
  }
  get_ans(0,0);
  cout<<ans<<endl;
}

 

F What Goes Up Must Come Down

  • 给出一个n个元素的数组,可以交换相邻的两个数,问至少需要交换多少次,使得数组是单峰的。

 

最小的那个数肯定是在数组的最左或者最后,那么把最小的那个移到边缘之后,就变成了一个子问题了。

因此从小打到枚举数,判断去左还是去右。

 

#include <bits/stdc++.h>

using namespace std;

int sum[100010], lt[100010],rt[100010];
int a[100010];
int max_n;
void add(int x){
  for(;x<=max_n;x+=x&-x)sum[x]++;
}

int get_sum(int x){
  int ret = 0;
  while(x>0){
    ret += sum[x];
    x-=x&-x;
  }
  return ret;
}

int find(int x){
  return get_sum(max_n)-get_sum(x);
}

int main(){
  int n;
  scanf("%d",&n);
  max_n = 0;
  for(int i = 1; i<= n; ++i){
    scanf("%d",&a[i]);
    max_n = max(max_n,a[i]);
  }
  
  memset(sum,0,sizeof(sum));
  for(int i = 1; i<=n; ++i){
      int pos = find(a[i]);
      lt[i] = pos;
      add(a[i]);
  }
  
  vector<int> pos;
  memset(sum,0,sizeof(sum));
  for(int i = n; i>0; --i){
    int ps = find(a[i]);
    rt[i] = ps;
    add(a[i]);
    pos.push_back(i);
  }
  
  long long ans = 0;
  for(int idx = 0; idx < pos.size();++idx){
    int i = pos[idx];
    ans += min(lt[i],rt[i]);
  }
  cout<<ans<<endl;
}

 

H Colorful Tree

  • 给出一棵树,每个节点对应一种颜色。有m个询问,修改节点的颜色或者对于某一种颜色,包含这些颜色的子树有多少条边。

直接求每一种颜色的树的大小有一丢丢难(至少我没想到)。那么就只能一个一个的加到对应的颜色里面,算他的贡献了。

加入一个节点时,其他颜色的节点只有两种情况,要不在子树,要不在父亲,

如果所有点都在子树或者在父亲,那么就是到最远和最近的父亲或者子树u,贡献就是(dis(x,u)+dis(x,v)-dis(u,v))/2。

否则贡献为0,因为子树到父亲必定经过这个点。

这个两种其实可以合在一起,父亲是u,子树是v,那么贡献就是(dis(x,u)+dis(x,v)-dis(u,v))/2。

因此添加或者删除的时候通过dfs序找出最近的父亲(dfs序-1)或者子树(dfs序+1),然后加上或者减掉贡献就好了。

 

using namespace std;
int mod = 998244353;
//int mod = 1e9+7;

vector<int> g[N];
int idx;
int dfn[N],dfn_c[N];
int c[N],num[N];
int fa[N][32], dp[N];
int n;

set<int> pos[N];

void dfs(int t, int f){
	dfn[t] = ++idx;
	dfn_c[idx] = t;
	fa[t][0] = f;
	dp[t] = dp[f]+1;
    for(int i = 1; i < 20; ++i){
      fa[t][i] = fa[fa[t][i-1]][i-1];
    }
	for(int u : g[t]){
		if(u==f)continue;
		dfs(u,t);
	}

}

int lca(int u, int v){
	if(dp[u] < dp[v]) swap(u,v);
	for(int i = 19; i >=0;--i){
		if(dp[fa[u][i]] >= dp[v]){
			u = fa[u][i];
		}
	}
	if(u==v) return u;
	for(int i = 19; i >=0;--i){
		if(fa[u][i]!=fa[v][i]){
			u = fa[u][i];
			v = fa[v][i];
		}
	}
	return fa[u][0];
}

int get_dis(int u, int v){
	int f = lca(u,v);
	return dp[u] + dp[v] - 2*dp[f];
}

int get_dis(int u, int v, int x){
	int dis1 = get_dis(u,x);
	int dis2 = get_dis(v,x);
	int dis3 = get_dis(u,v);
	return (dis1+dis2 - dis3)/2;
}

void change_dis(int idx, int flag){
    int col = c[idx];
    if(pos[col].empty()){
      num[col]=0;
      return;
    }
    auto r = pos[col].lower_bound(dfn[idx]);
    if(r==pos[col].begin() || r == pos[col].end()){
		auto l = pos[col].begin();
		auto r = pos[col].rbegin();
      num[col] += get_dis(dfn_c[*l], dfn_c[*r], idx) * flag;
    }
    else {
      auto l = r; --l;
      num[col] += get_dis(dfn_c[*l], dfn_c[*r], idx) * flag;
    }
}


void add(int idx, int flag){
	int col = c[idx];
	if(flag==1){
      change_dis(idx,flag);
	  pos[col].insert(dfn[idx]);
	}
	else {
      pos[col].erase(dfn[idx]);
      change_dis(idx,flag);
	}
}

int main(){
	clr(num);
	cin>>n;
	idx = 0;
	for(int i = 0; i < n-1; ++i){
		int a,b;
		cin>>a>>b;
		g[a].pb(b);
		g[b].pb(a);
	}
	dfs(1,0);

	for(int i = 1; i<=n; ++i){
		cin>>c[i];
		add(i,1);
	}

	int m;
	cin>>m;
	for(int i = 0; i < m; ++i){
		char op;
		int idx;
		cin>>op>>idx;
		if(op=='Q') {
			if(pos[idx].empty()) cout<<-1<<endl;
			else cout<<num[idx]<<endl;
		}
		else {
			int new_c;
			cin>>new_c;
			add(idx,-1);
			c[idx]=new_c;
			add(idx,1);
		}
	}
}

 

I Sixth Sense

  • 给出两个序列a,b。对b进行调整使得\sum (b[i]]>a[i])最大。如果有多个b,选字典序最大的。

 

最大值可以通过贪心。对于a[i],upper_bound出b。如果a是有序的话,b的结果是单调的。因此可以通过指针移动来解决。复杂度O(n)

然后在选择字典序最大时,枚举每一个a可以选择的b,然后去看剩下的a和b是否能满足条件。

枚举b需要二分,对比a大和比a小分别讨论,因此比a大贡献为1.

 

枚举时,先构造出可以用的a和b,这样时间就是O(n),在枚举加检查就是O(nlog(n))

总时间就是O(n^2log(n))

 

int n;
int p[N],f[N];
int vb[N];

int get_ans(vector<int>& a, vector<int>& b){
    int num = 0;
	int pos = 0;
    for(int i = 0; i < a.size(); ++i){
      while(pos<b.size()&&(vb[pos] || b[pos]<=a[i]))pos++;
      if(pos<b.size()){
        num++;
		pos++;
      }
    }

    return num;
}

int find(vector<int>&a, vector<int>&b, int s_idx, int e_idx, int tot){
	int l = s_idx, r = e_idx;
	while(l+1<r){
		int mid = (l+r)>>1;
		vb[mid]=1;
		int tmp = get_ans(a,b);
		if(tmp >= tot){
			l = mid;
		}
		else {
			r = mid;
		}
		vb[mid]=0;
	}
	return l;
}

int main(){
	cin>>n;
    vector<int> a,b;
    vector<pair<int,int>> pa;
	fr(i,0,n){
		cin>>p[i];
		pa.pb(mp(p[i],i));
	}
	fr(i,0,n){
		cin>>f[i];
        b.pb(f[i]);
	}

    sort(pa.begin(), pa.end());
	sort(b.begin(), b.end());

	for(int j = 0; j < pa.size();++j){
		a.pb(pa[j].first);
	}

	int tot = get_ans(a,b);
	vector<int> ans;
	fr(i,0,n){
        vector<int> a;
        for(int j = 0; j < pa.size();++j){
          if(pa[j].second<=i)continue;
          a.pb(pa[j].first);
        }
        vector<int> tmp;
        for(int j = 0; j < b.size();++j){
          if(vb[j])continue;
          tmp.pb(b[j]);
        }
        b = tmp;
        for(int j = 0; j < b.size();++j){
          vb[j] = 0;
        }
		int pos = upper_bound(b.begin(), b.end(), p[i])-b.begin();
		int ret = find(a,b,pos,b.size(),tot-1);
		if(ret!=b.size()){
			vb[ret]=1;
			if(get_ans(a,b)==tot-1){
				ans.pb(b[ret]);
				tot--;
				continue;
			}
			vb[ret]=0;
		}
		ret = find(a,b, 0,pos, tot);
		vb[ret]=1;
		ans.pb(b[ret]);
	}
	for(int x : ans)pf("%d ",x);
	pf("\n");
}

J Jokewithpermutation

  • 给出一个字符串,他是一个n的排列组成的串。还原他的排列。

暴力搜就好了。

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

int num[100];
string s;
int n;
vector<int> ans;
bool dfs(int idx){
  if(idx>=n){
    int max_v = 0;
    for(int i = 0; i < ans.size();++i){
    max_v = max(max_v, ans[i]);
    }
    for(int i = 1; i <=max_v; ++i){
      if(num[i]==0) return 0;
    }
    return 1;
  }
  int ret = 0;
  for(int i = 0; i < 2; ++i){
    ret = ret * 10 + s[idx+i]-'0';
    if(num[ret]==0){
      num[ret]=1;
      ans.push_back(ret);
      if(dfs(idx+i+1))return 1;
      num[ret]=0;
      ans.pop_back();
    }
  }
  return 0;
}

int main(){
  cin>>s;
  n = s.size();
  dfs(0);
  for(int x : ans) printf("%d ",x);printf("\n");
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值