2019 南昌区域赛 CEGLM 题解 & lagrange 插值
Posted bringlu
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了2019 南昌区域赛 CEGLM 题解 & lagrange 插值相关的知识,希望对你有一定的参考价值。
B. A Funny Bipartite Graph
状压 dp ,利用了原题中选完左边点集,那么右边在 左边编号最大的那个数 之前的所有点都要选的性质,可以优化到 (O(n cdot 2^n))。由于懒得补,所以写个算法溜了。(逃
C. And and Pair
题目大意:给你一个数 n 的二进制表示。你需要找出小于 n 有多少二元组 (i,j) 满足以下条件。
做法:后两个条件告诉我们, 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 全加起来就行了。由于你是考虑最高位为 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
",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
", 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
",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
");
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
", 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
题意:设
给你 t,x,y ,求
题解:可以发现
所以
先讨论第二项,因为这个比较好推。当 (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)) 做出来了。
再看第一项
如果使用里面的式子直接对每一个最高 k+1 次方的多项式进行拉格朗日插值,那么时间复杂度会来到 (O(t^2)) 。但是经过观察发现,这个式子还是一个最高 t+1 次方的多项式,这样我们可以这么化这个式子。
化到这一步,就可以直接对这个式子进行拉格朗日插值了。
#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;
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
",ans);
return 0;
}
附:拉格朗日插值公式
给出 n 个不连续的坐标的插值法:
注:这里 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
",lagrange(n-1,a,b,m));
return 0;
}
从 1 到 n 这种坐标连续的拉格朗日插值法:
设
代码直接看上面的 M 题即可。
这些代码都是从 0 开始存的,且 n 为多项式最高次幂次数。
以上是关于2019 南昌区域赛 CEGLM 题解 & lagrange 插值的主要内容,如果未能解决你的问题,请参考以下文章