Loading

2019 南昌区域赛 CEGLM 题解 & lagrange 插值

B. A Funny Bipartite Graph

状压 dp ,利用了原题中选完左边点集,那么右边在 左边编号最大的那个数 之前的所有点都要选的性质,可以优化到 \(O(n \cdot 2^n)\)。由于懒得补,所以写个算法溜了。(逃

C. And and Pair

题目大意:给你一个数 n 的二进制表示。你需要找出小于 n 有多少二元组 (i,j) 满足以下条件。

\[0\leq j \leq i \leq n \]

\[i \& n = i \]

\[i \& j = 0 \]

做法:后两个条件告诉我们, i 要在n二进制数为 1 的位置上选是 1 or 0,j 要在 n 二进制数为 0 的位置上选是 1 or 0。 i,j 其余位置全为 0。(接下来全是二进制表示)我们枚举 i 中是 1 的最高位,这样我们就发现后面的所有数可以在没有前导零这个问题困扰下,去取最高位后面的数。

设取到当前这一位后面 0 的个数有 a 个,1 的个数有 b 个,i 中 1 的个数为 c \((0 \leq c \leq b)\) ,则 j 有 \(2^{|S|-c}\) 种选法(|S|为当前枚举到的长度),i 中 1 的个数为 c 有 \(C_b^c\) 个。

那么,当前这一位(假如是 1 )的答案为:

\[ans = \sum_{c=0}^b C_b^c 2^{|S|-c}1^c = 2^{|S|-b}(\sum_{c=0}^b C_b^c 1^c 2^{b-c}) = 3^b 2^{|S|-b} = 3^b 2^a \]

最后把 ans 全加起来就行了。由于你是考虑最高位为 1 这样考虑的,所以还有一个 i = 0 需要特意加一个 1 。意识流下划线代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define IL inline 
#define ri register int
typedef long long LL;

const int N = 1e5 + 3;
const int mod = 1e9 + 7;
char s[N];
int _2[N],_3[N];

int main() {
	int T; scanf("%d",&T);
	_2[0] = _3[0] = 1;
	for(int i=1;i<=1e5;i++) {
		_2[i] = 2LL * _2[i-1] % mod;
		_3[i] = 3LL * _3[i-1] % mod;
	}
	while(T--) {
		scanf("%s",s);
		int len = strlen(s);
		int ans, _0, _1;
		ans = _0 = _1 = 0;
		for(int i=len-1;i>=0;i--) {
			if(s[i] == '1') {
				ans = (ans + 1LL*_2[_0]*_3[_1]%mod) % mod;
				_1++;
			}
			else _0++;
		}
		ans = (ans + 1) % mod;
		printf("%d\n",ans);
	}
}

队友代码:(他说用了 dp ,但是我不知道 dp 咋做。)

#include <bits/stdc++.h>

using namespace std;

const int MOD = 1e9 + 7;

char s[100005];
int f[100005];

int main() {
	int T;
	//freopen("1.txt", "r", stdin);
	cin >> T;
	while (T--) {
		scanf("%s", s);
		int n = strlen(s);
		int lst = 1, ans = 1;
		for (int i = n - 1; i >= 0; i--) {
			if (s[i] == '0')
				(lst <<= 1) %= MOD, f[i] = 0;
			else {
				f[i] = lst;
				lst = (1ll * lst * 2 + f[i]) % MOD;
			}
		}
		for (int i = n - 1; i >= 0; i--)
			(ans += f[i]) %= MOD;
		printf("%d\n", ans);
	}
	return 0;
}
E. Bob's Problem

题目大意:给你一张图,有边权,然后每条边可能为白边或者黑边。你需要找到连通整个图的最大边权和,但是你不能选择超过 k 个白边。

做法:最大生成树。你就把黑边全选了,然后对白边套最大生成树做法就行。我在写的时候只记得特判有没有超过 k ,忘了特判原图是否连通了……

#include<cstdio>
#include<cstring>
#include<vector>
#include<algorithm>
using namespace std;
#define IL inline
typedef long long LL;

const int N = 50000 + 3;
const int M = 500000 + 3;

struct Edge {
	int u,v,w,col;
	Edge(int u=0,int v=0,int w=0,int col=0):u(u),v(v),w(w),col(col) {}
	IL bool operator < (const Edge& rhs) const {
		return col < rhs.col || (col == rhs.col && w > rhs.w);
	}
};

struct MST {
	LL ans;
	int n,m,k,bcnt;
	int pa[N];
	int sz[N];
	bool ing[M];
	vector<Edge> edges;
	
	void init(int n,int m,int k) {
		this->n = n; this->m = m; this->k = k;
		ans = 0; bcnt = 0;
		fill(ing,ing+m,0);
		edges.clear();
		for(int i=0;i<n;i++) { pa[i] = i; sz[i] = 1;}
	}
	
	int findset(int x) { return x == pa[x] ? x : pa[x] = findset(pa[x]);}
	void merge(int fx,int fy) {
		if(sz[fx] <= sz[fy]) {
			pa[fx] = fy;
			sz[fy] += sz[fx];
		}
		else {
			pa[fy] = fx;
			sz[fx] += sz[fy];
		}
	}
	
	IL LL solve() {
		int cnt = 0,vcnt = 0;
		sort(edges.begin(),edges.end());
		for(int i=0;i<bcnt;i++) {
			Edge &e = edges[i];
			int u = e.u, v = e.v, w = e.w;
			int fu = findset(u), fv = findset(v);
			if(fu != fv) { merge(fu,fv); vcnt++;}
			ans = ans + w;
		}
		for(int i=bcnt;i<m;i++) {
			Edge &e = edges[i];
			int u = e.u, v = e.v, w = e.w;
			int fu = findset(u), fv = findset(v);
			if(fu != fv) {
				cnt++; vcnt++;
				ing[i] = true;
				merge(fu,fv);
				ans = ans + w;
			}
			if(cnt >= k) {
				return vcnt == n-1 ? ans : -1;
			}
		}
		for(int i=bcnt;i<m&&cnt<k;i++) {
			if(!ing[i]) {
				ans = ans + edges[i].w;
				cnt++;
				ing[i] = true;
			}
		}
		return vcnt == n-1 ? ans : -1;
	}
};

MST solver;

int main() {
	int T; scanf("%d",&T);
	while(T--) {
		int n,m,k; scanf("%d%d%d",&n,&m,&k);
		solver.init(n,m,k);
		for(int i=0;i<m;i++) {
			int u,v,w,col;
			scanf("%d%d%d%d",&u,&v,&w,&col); u--; v--;
			solver.edges.push_back(Edge(u,v,w,col));
			if(!col) solver.bcnt++;
		}
		printf("%lld\n",solver.solve());
	}
	return 0;
}
G. Eating Plan

题意:给你一个长度为 n 的排列,你只能选取连续的一段,获得数值为这一段每一个元素的阶乘之和再对 t 取余。再给你 m 次查询,问你最少选多长的一段,可以使得获得的数值大于等于每次询问给你的数 k。

做法:可以发现 \(t = 998857459 = 461 * 773 * 2803\) ,于是大于 2803 的数的阶乘对 t 取余等于0。那么这个排列的阶乘就变成了一段0,然后一个数,再一段0……如此反复。然后乱搞即可。

附队友代码:

#include <bits/stdc++.h>

using namespace std;

const int MOD = 998857459;

int num[3005];
int pos[3005], t;
int a[100005], sum[100005];
int len[100005];

int main() {
	//freopen("1.txt", "r", stdin);
	int n, m;
	scanf("%d%d", &n, &m);
	num[1] = 1;
	for (int i = 2; i <= 2803; i++)
		num[i] = 1ll * num[i - 1] * i % MOD;
	for (int i = 1; i <= n; i++) {
		scanf("%d", &a[i]);
		if (a[i] >= 2803) a[i] = 0;
		else
			pos[++t] = i, a[i] = num[a[i]];
	}
	for (int i = 1; i <= n; i++)
		sum[i] = (sum[i - 1] + a[i]) % MOD;
	for (int i = 1; i <= t; i++) {
		for (int j = i; j <= t; j++) {
			int l = pos[i], r = pos[j], ll = pos[i - 1];
			len[r - l + 1] = max(len[r - l + 1], ((sum[r] - sum[ll]) % MOD + MOD) % MOD);
		}
	}
	for (int i = 1; i <= n; i++)
		len[i] = max(len[i], len[i - 1]);
	int k;
	for (int i = 1; i <= m; i++) {
		scanf("%d", &k);
		if (len[n] < k)	{
			printf("-1\n");
			continue;
		}
		int l = 1, r = n, mid;
		while (l < r) {
			mid = l + r >> 1;
			if (len[mid] >= k)
				r = mid;
			else l = mid + 1;
		}
		printf("%d\n", r);
	}
	return 0;
}
L. Who is the Champion

题意:签到题(我还没搞懂题意啥意思呢,丢个队友代码溜了)

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<string>
#include<cmath>
#include<map>
#include<stack>
#include<vector>
#include<queue>
#include<set>
#include<algorithm>
#define max(a,b)   (a>b?a:b)
#define min(a,b)   (a<b?a:b)
#define swap(a,b)  (a=a+b,b=a-b,a=a-b)
#define memset(x,y) memset(x,y,sizeof(x))
#define ll long long
using namespace std;
int n;
int a[110][110];
struct team {
	int num;
	int boll;
	int s;
} t[110];
bool cmp(team t1,team t2) {
	if(t1.s!=t2.s)return t1.s>t2.s;
	else return t1.boll>t2.boll;
}
int main() {
	//freopen(".in","r",stdin);
	//freopen(".out","w",stdout);
	cin>>n;
	for(int i=1; i<=n; i++) {
		for(int j=1; j<=n; j++) {
			cin>>a[i][j];
		}
	}
	if(n==1) {
		cout<<"1";
		return 0;
	}
	for(int i=1; i<=n; i++) {
		t[i].num=i;
		for(int j=1; j<=n; j++) {
			if(i==j)continue;
			if(a[i][j]>a[j][i])t[i].s+=3,t[i].boll+=(a[i][j]-a[j][i]);
			else if(a[i][j]==a[j][i])t[i].s+=1;
			else t[i].boll+=(a[i][j]-a[j][i]);
		}
	}
	sort(t+1,t+n+1,cmp);
	if(t[1].s==t[2].s&&t[1].boll==t[2].boll)cout<<"play-offs";
	else cout<<t[1].num;
	return 0;
}
M. XOR Sum

题意:设

\[f(i,k) = 1 \oplus 2 \oplus 3 \oplus .. \oplus i^k \]

给你 t,x,y ,求

\[\sum_{k=1}^t \sum_{i=x}^y f(i,k) \mod (1e9+7) \]

题解:可以发现

\[f(x) = \begin{cases} i^k , i^k \equiv 0 \mod 4\\ 1 , i^k \equiv 1 \mod 4\\ i^k+1 , i^k \equiv 2 \mod 4\\ 0 , i^k \equiv 3 \mod 4\\ \end{cases} \]

所以

\[\sum_{k=1}^t \sum_{i=x}^y f(i,k) = \sum_{k=1}^t \sum_{i=x}^y i^k [i^k \mod 2 =0] + \sum_{k=1}^t \sum_{i=x}^y [i^k \mod 4 = 1 \text{ or } 2] \]

先讨论第二项,因为这个比较好推。当 \(i \equiv 0(\mod 4)\) 时,无论 k 取多少都没有贡献。当 \(i \equiv 1(\mod 4)\) 时,k 可以取任意值,都有贡献。当 \(i \equiv 2(\mod 4)\) 时,k 只能取 1。当 \(i \equiv 3(\mod 4)\) 时,k 只有为偶数时才有贡献。这样第二项就可以 \(O(1)\) 做出来了。

再看第一项

\[\sum_{k=1}^t \sum_{i=x}^y i^k [i^k \mod 2 =0] = \sum_{k=1}^t \sum_{i=\lfloor\frac{x+1}{2}\rfloor}^{\lfloor \frac{y}{2} \rfloor} (2i)^k = \sum_{k=1}^t (\sum_{i=1}^{\lfloor \frac{y}{2} \rfloor} (2i)^k - \sum_{i=1}^{\lfloor\frac{x+1}{2}\rfloor} (2i)^k) \]

如果使用里面的式子直接对每一个最高 k+1 次方的多项式进行拉格朗日插值,那么时间复杂度会来到 \(O(t^2)\) 。但是经过观察发现,这个式子还是一个最高 t+1 次方的多项式,这样我们可以这么化这个式子。

\[\sum_{k=1}^t \sum_{i=\lfloor\frac{x+1}{2}\rfloor}^{\lfloor \frac{y}{2} \rfloor} (2i)^k = \sum_{i=\lfloor\frac{x+1}{2}\rfloor}^{\lfloor \frac{y}{2} \rfloor} \sum_{k=1}^t (2i)^k = \sum_{i=\lfloor\frac{x+1}{2}\rfloor}^{\lfloor \frac{y}{2} \rfloor} \frac{2i(1-(2i)^t)}{1-2i} \\= \sum_{i=1}^{\lfloor \frac{y}{2} \rfloor} \frac{2i(1-(2i)^t)}{1-2i} - \sum_{i=1}^{\lfloor\frac{x+1}{2}\rfloor} \frac{2i(1-(2i)^t)}{1-2i} \]

化到这一步,就可以直接对这个式子进行拉格朗日插值了。

#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long LL;
#define IL inline
#define ri register int

const int N = 1e5 + 5;
const LL mod = 1e9 + 7;

LL jc[N],ijc[N];

LL ksm(LL a,LL b, LL p) {
	LL res = 1LL;
	while(b) {
		if(b&1LL) res = res * a % p;
		a = a * a % p;
		b >>= 1LL;
	}
	return res;
}

//(0,y0),(1,y1),..,(n,yn) -> (xi,y_xi)
LL pre[N],suf[N];
LL lagrange(int n,LL *y,LL xi) {
	if(xi <= n) return y[xi];
	pre[0] = xi % mod; suf[n+1] = 1;
	for(int i=1;i<=n;i++) pre[i] = (xi-i)%mod*pre[i-1]%mod;
	for(int i=n;i>=0;i--) suf[i] = (xi-i)%mod*suf[i+1]%mod;
	LL ans = y[0]%mod*suf[1]%mod*ijc[n]%mod;
        if(n&1) ans = -ans;
	for(int i=1;i<=n;i++) {
		LL now = y[i]*pre[i-1]%mod*suf[i+1]%mod*ijc[i]%mod*ijc[n-i]%mod;
		if((n-i)&1) ans = (ans - now + mod) % mod;
		else ans = (ans + now) % mod;
	}
	return ans;
}

LL calc(LL L, LL R,LL a) {
	LL sumR = (R - a + 4) / 4;
	LL sumL = (L-1-a + 4) / 4;
	return (sumR - sumL + mod) % mod;
}

LL t,x,y;
LL S[N];

int main() {
	scanf("%lld%lld%lld",&t,&x,&y);
	jc[0] = ijc[0] = 1;
	for(int i=1;i<N;i++) jc[i] = jc[i-1] * i % mod;
	ijc[N-1] = ksm(jc[N-1],mod-2,mod);
	for(int i=N-2;i>=0;i--) ijc[i] = (i+1) * ijc[i+1] % mod;
	
	S[0] = 0;
	for(int i=1;i<=t+1;i++) {
		S[i] = ((ksm(2*i,t+1,mod)-2*i+mod) * ksm(2*i-1,mod-2,mod)%mod + S[i-1]) % mod;
	}
	
	LL ans = (lagrange(t+1,S,y/2) - lagrange(t+1,S,(x+1)/2-1) + mod) % mod;
	ans = (ans + calc(x,y,2)) % mod;
	ans = (ans + t * calc(x,y,1)) % mod;
	ans = (ans + t/2 * calc(x,y,3)) % mod;
	printf("%lld\n",ans);
	return 0;
}

附:拉格朗日插值公式

给出 n 个不连续的坐标的插值法:

\[f(k) = \sum_{i=0}^n y_i \prod_{i=0,i\not=j}^n \frac{k-x_j}{x_i-x_j} \]

注:这里 lagrange 函数里的 n 是多项式最高项次数。

#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
using namespace std;
#define IL inline
#define ri register int 
const int mod = 998244353;

int ksm(int a,int b,int p) {
	int res = 1;
	while(b) {
		if(b&1) res = 1LL * res * a % p;
		a = 1LL * a * a % p;
		b >>= 1;
	}
	return res;
}

int lagrange(int n,int *x,int *y,int xi) {
	int ans = 0;
	for(int i=0;i<=n;i++) {
		int fz = 1, fm = 1;
		for(int j=0;j<=n;j++) if(i != j) {
			fz = 1LL * fz * (xi - x[j]) % mod;
			fm = 1LL * fm * (x[i]-x[j]) % mod;
		}
		ans = (ans + 1LL*y[i]*fz%mod*ksm(fm,mod-2,mod) % mod) % mod;
	}
	return (ans + mod) % mod;
}

const int N = 2e3 + 3;

int n,m;
int a[N],b[N];

int main() {
	scanf("%d%d",&n,&m);
	for(int i=0;i<n;i++) scanf("%d%d",&a[i],&b[i]);
	printf("%d\n",lagrange(n-1,a,b,m));
	return 0;
}

从 0 到 n 这种坐标连续的拉格朗日插值法:

\[pre_i=\prod_{j=0}^i k-j \\ suf_i = \prod_{j=i}^n k-j \\ f(k) = \sum_{i=0}^n y_i \prod_{i=0,i\not=j}^n \frac{k-j}{i-j} = \sum_{i=0}^n y_i \frac{pre_{i-1}\cdot suf_{i+1}}{i! \cdot (n-i)!} \]

代码直接看上面的 M 题即可。

这些代码都是从 0 开始存的,且 n 为多项式最高次幂次数。

posted @ 2020-03-26 23:46  bringlu  阅读(386)  评论(0编辑  收藏  举报