9.8-9.9多校互测与牛客网提高一测

9.8-9.9多校互测与牛客网提高一测

多校互测

貌似是比较尴尬,本来100+40+30 (->) 70+30+30 。原因竟然是(dots)
貌似是只做了(day1)至于做不做(day2)还是两说,但是先写题解为妙。

A

这是一道语文题,出题人毒瘤到水题不能告诉你这题目是什么意思(dots)
然后考试的时候读了好长时间的题面,硬是没想出来。
然后考试最后半小时终于看懂了题目什么意思,但就是不想写正解了,于是索性打个暴力,更气人的是,暴力打完还剩(25min),还是不想写正解,于是想优化就加上了离散化代码到达了(50)行。考完试才知道正解(20)行。

于是贴一下我(70)分离散化代码

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<queue>
#include<cmath>
#define LL long long
using namespace std;
struct edge {
	int id,a,ans;
} e[100001];
int n,b[100001],cnt,vis[100001],d[100001];
bool cmp(edge x,edge y) {
	return x.a<y.a;
}
bool cml(edge x,edge y) {
	return x.id<y.id;
}
void calc(int x) {
	int p=lower_bound(b+1,b+1+cnt,e[x].a)-b;
	int ans=0;
	for(int i=p-1; i>=1; i--) {
		if(e[x].a%b[i]==0)ans+=vis[i];
		if(b[i]==0)break;
	}
	ans+=vis[p]-1;
	e[x].ans=ans;
}
int main() {
	freopen("A.in","r",stdin);
	freopen("A.out","w",stdout);
	scanf("%d",&n);
	for(int i=1; i<=n; i++) scanf("%d",&e[i].a),e[i].id=i,e[i].ans=0;
	sort(e+1,e+1+n,cmp);
	for(int i=1; i<=n; i++) {
		if(e[i].a!=e[i-1].a)b[++cnt]=e[i].a;
		vis[cnt]++;
	}
	for(int i=1; i<=n; i++)d[i]=lower_bound(b+1,b+1+cnt,e[i].a)-b;
	for(int i=1; i<=n; i++)calc(i);
	sort(e+1,e+1+n,cml);
	for(int i=1; i<=n; i++)printf("%d
",e[i].ans);
	return 0;
}

B

就是给一段序列,然后求字母的出现次数最大的减出现次数最小的
我一开始就想,怎么样才能存储字母呢,我想了个自以为不错的方法——树状数组(最后发现前缀和统计即可)

//30pts
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<queue>
#include<cmath>
using namespace std;
int vis[27],tot,tree[27][1000002],n,fin[27],answ;
int to_num(char s){
	return s-96;
}
int lowbit(int k){
	return k&(-k);
}
void add(int id,int x,int y){
	while(x<=n){
		tree[id][x]+=y;
		x+=lowbit(x);
	}
}
int ask(int id,int x){
	int ans=0;
	while(x!=0){
		ans+=tree[id][x];
		x-=lowbit(x);
	}
	return ans;
}
void work(int l,int r){
	int maxn=0,minn=0x7fffffff;
	for(int i=1;i<=tot;i++){
		int p=ask(fin[i],r)-ask(fin[i],l-1);
		if(p==0)continue;
		maxn=max(maxn,p);
		minn=min(minn,p);
	}
	answ=max(answ,maxn-minn);
}
int main()
{
	// freopen("B.in","r",stdin);
	// freopen("B.out","w",stdout);
	scanf("%d",&n);
	for(int i=1;i<=n;i++){
		char s;
		cin >> s;
		int p=to_num(s);
		if(!vis[p]){
			vis[p]=1;
			fin[++tot]=p;
		}
		add(p,i,1);
	}
	for(int i=1;i<=n;i++)
		for(int j=i;j<=n;j++)
			work(i,j);
	printf("%d",answ);
	return 0;
}

意思就是枚举左右端点,然后跑树状数组即可

但是我又通过询问李小浪,(Get)(60pts)的方法:
用前缀和统计,然后枚举两个字符,然后再枚举每个数字在哪个区间出现的次数。也就是说用前缀和表示为((a_j-a_i)-(b_j-b_i))的值最小对吧,然后换一下形式((a_j-b_j)-(a_i-b_i)),嗯,因为(j)肯定比(i)大,所以枚举的时候就能存下来。从而可以拿到(60)分的好成绩。

C

题目的意思很简单啊,就是每一点的最短路的最后一条路径删去。考完试dalao们激情交流,枚举到倒数第二个点然后加上第二个点最短路啥啥的,但是仔细一想不对,因为有可能最短路就改变了。然后我暴力(n)(spfa)拿到30pts,他们只拿到了10pts。

//30pts code
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<queue>
#include<cmath>
#define LL long long

using namespace std;

struct edge {
	int next,to,dis;
} e[400001];
int head[400001],tot,n,m,dis[100001],minn[100001],vis[100001],pre[100001];
void add(int x,int y,int z) {
	e[++tot].next=head[x];
	e[tot].to=y;
	head[x]=tot;
	e[tot].dis=z;
}

void spfa(int s) {
	queue<int>q;
	q.push(s);
	memset(vis,0,sizeof(vis));
	memset(dis,0x3f,sizeof(dis));
	dis[s]=0;
	vis[s]=1;
	while(!q.empty()) {
		int u=q.front();
		q.pop();
		vis[u]=0;
		for(int i=head[u]; i; i=e[i].next) {
			int v=e[i].to;
			if(dis[v]>dis[u]+e[i].dis) {
				pre[v]=u;
				dis[v]=dis[u]+e[i].dis;
				if(!vis[v])q.push(v),vis[v]=1;
			}
		}
	}
}
void spfa_(int s,int x,int y) {
	queue<int>q;
	q.push(s);
	memset(vis,0,sizeof(vis));
	memset(dis,0x3f,sizeof(dis));
	dis[s]=0;
	vis[s]=1;
	while(!q.empty()) {
		int u=q.front();
		q.pop();
		vis[u]=0;
		for(int i=head[u]; i; i=e[i].next) {
			int v=e[i].to;
			if((u==x && v==y) || (v==x && u==y))continue;
			if(dis[v]>dis[u]+e[i].dis) {
				dis[v]=dis[u]+e[i].dis;
				if(!vis[v])q.push(v),vis[v]=1;
			}
		}
	}
}
int main() {
	// freopen("C.in","r",stdin);
	// freopen("C.out","w",stdout);
	scanf("%d%d",&n,&m);
	for(int i=1; i<=m; i++) {
		int a,b,c;
		scanf("%d%d%d",&a,&b,&c);
		add(a,b,c);
		add(b,a,c);
	}
	for(int i=1; i<=n; i++)minn[i]=0x7fffffff;
	spfa(1);
	for(int i=2;i<=n;i++) {
		spfa_(1,i,pre[i]);
		minn[i]=min(minn[i],dis[i]);
	}
	for(int i=2; i<=n; i++) {
		if(minn[i]!=1061109567)
			printf("%d
",minn[i]);
		else if(minn[i]==1061109567 || minn[i]==2147483647)printf("-1
");
	}
	return 0;
}

赛后看题解,真恶心人,最小路径树是什么鬼?are you sure noip kao ?
不学了

牛客网比赛

T1中位数

怎么说呢暴力本来是要跑60的啊,n方logn可是能过的啊,偏偏就30

//30pts
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<queue>
using namespace std;
const int N = 100001;
int n,len,maxn;
int a[N],b[N];
int main(){
	scanf("%d%d",&n,&len);
	for(int i=1;i<=n;i++){scanf("%d",&a[i]);b[i]=a[i];}
	for(int l=1,r=l+len-1;r<=n;l++,r++){
		sort(b+l,b+1+r);
		if(len%2==1)maxn=max(maxn,b[l+(len+1)/2-1]);
		else maxn=max(maxn,b[l+len/2-1]);
		for(int i=l;i<=r;i++)b[i]=a[i];
	}
	printf("%d",maxn);
}

然后第二档60pts主席树查找区间第k大,弃疗弃疗,直接看正解

正解是枚举一个可能值,然后会发现可以二分,然后在记录。
对于第三档数据,直接枚举最终可能的答案x。把数字(>)x的记为1, (le)x的记为-1。检验是否存在和$ge(0的区间。可以维护一个前缀和的前缀最大值。时间复杂度)O(Mn)$,
M为不同的数的个数。

#include <bits/stdc++.h>

using namespace std;

const int maxn = 100005;

int a[maxn], n, L;

bool test(int z)
{
	static int b[maxn];
	for(int i = 1;i <= n;i ++)
		if (a[i] >= z) b[i] = 1; else b[i] = -1;
	for(int i = 1, mi = (1 << 30);i <= n;i ++)
	{
		if (i >= L) mi = min(mi, b[i - L]);
		b[i] += b[i - 1];
		if (i >= L && b[i] - mi > 0) return 1;
	}
	return 0;
}

int main()
{
	freopen("median.in","r",stdin),freopen("median.out","w",stdout);
	scanf("%d%d", &n, &L);
	for(int i = 1;i <= n;i ++) scanf("%d", &a[i]);
	int l = 0, r = int(1e9), tmp;
	for(int mid;l <= r;)
	{
		mid = l + r >> 1;
		if (test(mid)) tmp = mid, l = mid + 1; else r = mid - 1;
	}
	printf("%d
", tmp);
	return 0;
}

数数字

唯一一道暴力分多的题目。。
30pts不用说了,直接上代码了

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<queue>
#include<cstring>
#define LL long long
using namespace std;
LL l,r,l1,r1;
int f[50000001];
int ans;
int fw(int x) {
	int z=1;
	while(x) {
		if(x<50000001)
			if(f[x])return z*f[x];
		z=z*(x%10);
		x/=10;
	}
	if(x<50000001)
		f[x]=z;
	return z;
}
int main() {
	// freopen("1.in","r",stdin);
	f[1]=1;
	f[2]=2;
	f[3]=3;
	f[4]=4;
	f[5]=5;
	f[6]=6;
	f[7]=7;
	f[8]=8;
	f[9]=9;
	f[10]=0;
	for(int i=11; i<=1000000; i++)
		f[i]=f[i/10]*(i%10);
	scanf("%lld%lld%lld%lld",&l,&r,&l1,&r1);
	for(int i=l; i<=r; i++)
		if(fw(i)>=l1&&fw(i)<=r1)ans++;
	printf("%d",ans);
}

就是恶心的打表记忆化加暴力。但还是40pts。
然后,正解就是数位(dp)
先贴一下std压一下

//code - std
#include <bits/stdc++.h>

using namespace std;

typedef long long ll;

int co[10][10];
ll l,r,l1,r1, pw[10][100];

inline int merge(int i, int cr, int b)
{
	return (cr > b ? 2 : (cr == b ? i : 0));
}

ll calc_no_zero(ll x)
{
	if (x <= 0) return 0;
	static ll f[2][3][60][38][27][22];
	memset(f,0,sizeof f);
	int cr = 0;
	f[0][1][0][0][0][0] = 1;
	ll ans = 0, val;
	for(;x;x /= 10, cr ^= 1)
	{
		int bit = x % 10;
		memset(f[cr ^ 1], 0, sizeof f[cr]);
		for(int i = 0;i < 3;i ++)
			for(int n_2 = 0;n_2 < 60;n_2 ++)
				for(int n_3 = 0;n_3 < 38;n_3 ++)
					for(int n_5 = 0;n_5 < 27;n_5 ++)
						for(int n_7 = 0;n_7 < 22;n_7 ++)
							if (val = f[cr][i][n_2][n_3][n_5][n_7])
							{
								ll q1 = r1 / pw[2][n_2] / pw[3][n_3] / pw[5][n_5] / pw[7][n_7];
								ll p1 = (l1 - 1) / pw[2][n_2] / pw[3][n_3] / pw[5][n_5] / pw[7][n_7];
								if (q1 >= 1 && p1 <= 0) ans += val;
								if (q1 <= 0) continue;
								for(int p = 1;p < 10;p ++)
								{
									int ni = merge(i, p, bit);
									int c_2 = n_2 + co[p][2];
									int c_3 = n_3 + co[p][3];
									int c_5 = n_5 + co[p][5];
									int c_7 = n_7 + co[p][7];
									if (c_2 >= 60 || c_3 >= 38 || c_5 >= 27 || c_7 >= 22) continue;
									f[cr ^ 1][ni][c_2][c_3][c_5][c_7] += val;
								}
							}
	}
	for(int i = 0;i < 2;i ++)
		for(int n_2 = 0;n_2 < 60;n_2 ++)
				for(int n_3 = 0;n_3 < 38;n_3 ++)
					for(int n_5 = 0;n_5 < 27;n_5 ++)
						for(int n_7 = 0;n_7 < 22;n_7 ++)
							if (val = f[cr][i][n_2][n_3][n_5][n_7])
							{
								ll q1 = r1 / pw[2][n_2] / pw[3][n_3] / pw[5][n_5] / pw[7][n_7];
								ll p1 = (l1 - 1) / pw[2][n_2] / pw[3][n_3] / pw[5][n_5] / pw[7][n_7];
								if (q1 >= 1 && p1 <= 0) ans += val;
							}
	return ans;
}

ll calc_with_zero(ll x)
{
	if (l1) return 0;
	static ll g[2][3][2][2];
	memset(g,0,sizeof g);
	int cr = 0;
	ll ans = 0;
	g[0][1][0][0] = 1;
	for(;x;x /= 10, cr ^= 1)
	{
		int bit = x % 10;
		memset(g[cr ^ 1], 0, sizeof g[cr]);
		for(int i = 0;i < 3;i ++)
			for(int j = 0;j < 2;j ++)
				for(int hd = 0;hd < 2;hd ++)
					if (g[cr][i][j][hd])
					{
						if (hd && j) ans += g[cr][i][j][hd];
						for(int p = 0;p < 10;p ++)
						{
							int ni = merge(i, p, bit);
							int nj = (j | (p == 0));
							int hdt = (p > 0);
							g[cr ^ 1][ni][nj][hdt] += g[cr][i][j][hd];
						}
					}
	}
	return ans + g[cr][0][1][1] + g[cr][1][1][1];
}

ll calc(long long x)
{
	return calc_no_zero(x) + calc_with_zero(x);
}

int main()
{
	freopen("count.in","r",stdin),freopen("count.out","w",stdout);
	for(int i = 1;i < 10;i ++)
	{
		for(int j = 2;j < 10;j ++)
			for(int k = i;k % j == 0;k /= j) co[i][j] ++;
		pw[i][0] = 1;
		for(int j = 1;j <= 60;j ++) pw[i][j] = pw[i][j - 1] * i;
	}
	cin >> l >> r >> l1 >> r1;
	ll ans = 0;
	if (!l) ans += (l1 == 0), ++ l;
	if (l <= r) ans += calc(r) - calc(l - 1);
	cout << ans << endl;
	return 0;
}

正解可以使用记忆化搜索或者DP。这里介绍DP的做法。
由于数位乘积只是由0到9,可以把L1,R1分类讨论,假如区间包含0,则原来的数字分为包含至少一个零,和完全不包含0两类。前一类可以使用简单的数位DP计算。接下来介绍后一类。
由于只包含1到9,所以只用记录乘积的质因数分解中,出现了多少2, 3, 5, 7。题目中的L1,R1不超过10^18,可以计算出2最多为59个, 3最多为37, 5最多为26, 7最多为21。设F[i][c_2][c_3][c_5][c_7]表示考虑到第i位,乘积状态为c_2,c_3,c_5,c_7的数位DP情况。接着就是经典数位DP做法。空间可能比较紧,需要用滚动数组

保护

考试想了一个树剖的方法,但是临时改题面了,(...)好端端的100跑了。改完题面什么破题, are you sure noip tigaozu?

#include <bits/stdc++.h>

using namespace std;

const int maxn = 200005;

struct node
{
	int l,r,cnt;
}T[maxn * 100];

vector<int> lk[maxn];
int fa[maxn][20],dep[maxn],ord[maxn],cnt,n,m,ans[maxn];

void dfs(int now,int pre)
{
	dep[now] = dep[pre] + 1;
	fa[now][0] = pre;
	for(int i = 1;(1 << i) <= dep[now];i ++) fa[now][i] = fa[fa[now][i - 1]][i - 1];
	for(int i = 0;i < lk[now].size();i ++)
		if (lk[now][i] != pre)
			dfs(lk[now][i], now);	
}

void insert(int l,int r,int p,int &jd)
{
	if (!jd) jd = ++ cnt;
	T[jd].cnt ++;
	if (l == r) return;
	int mid = l + r >> 1;
	if (p <= mid) insert(l,mid,p,T[jd].l); else insert(mid + 1,r,p, T[jd].r);
}

int merge(int l,int r,int a,int b)
{
	if ((!a) || (!b)) return a + b;
	int jd = ++ cnt;
	T[jd].cnt = T[a].cnt + T[b].cnt;
	if (l == r) return jd;
	int mid = l + r >> 1;
	T[jd].l = merge(l,mid,T[a].l,T[b].l);
	T[jd].r = merge(mid + 1,r,T[a].r, T[b].r);
	return jd;
}

int find_k(int l,int r,int k,int jd)
{
	if (T[jd].cnt < k) return (1 << 30);
	if (l == r) return l;
	int mid = l + r >> 1;
	if (T[T[jd].l].cnt >= k) return find_k(l,mid,k,T[jd].l);
	return find_k(mid + 1,r,k - T[T[jd].l].cnt, T[jd].r);	
}

void dfs_w(int now,int pre)
{
	for(int i = 0;i < lk[now].size();i ++)
		if (lk[now][i] != pre) 
		{
			dfs_w(lk[now][i], now);
			ord[now] = merge(1,n,ord[now], ord[lk[now][i]]);
		}
}

int get_lca(int u,int v)
{
	if (dep[u] > dep[v]) swap(u,v);
	for(int i = 18;i + 1;i --)
		if (dep[fa[v][i]] >= dep[u]) v = fa[v][i];
	if (u == v) return u;
	for(int i = 18;i + 1;i --)
		if (fa[v][i] != fa[u][i]) v = fa[v][i], u = fa[u][i];
	return fa[u][0];
}

int main()
{
	freopen("guard.in","r",stdin),freopen("guard.out","w",stdout);
	scanf("%d%d", &n, &m);
	for(int i = 1,u,v;i < n;i ++)
	{
		scanf("%d%d", &u, &v);
		lk[u].push_back(v), lk[v].push_back(u);
	}
	dfs(1,0);
	for(int i = 1,u,v;i <= m;i ++)
	{
		scanf("%d%d", &u, &v);
		int p = get_lca(u,v);
		insert(1,n,dep[p],ord[u]),insert(1,n,dep[p],ord[v]);
	}
	dfs_w(1,0);
	int q;
	scanf("%d", &q);
	for(int i = 1;i <= q;i ++)
	{
		int u,k;
		scanf("%d%d", &u, &k);
		printf("%d
", max(0, dep[u] - find_k(1,n,k,ord[u])));
	}
	return 0;
}

题解也看不懂,挂惨。

原文地址:https://www.cnblogs.com/ifmyt/p/9615500.html