CSP-S模拟14
A. 莓良心
引用题解:考虑什么时候答案是0,显然是maxl<=minr。如果答案不是0,显然可以把值域缩小到[minr,maxl]之间,取到minr,maxl的两个区间变成了单点,把它们删除递归下去即可。
maxl相当于最小下界,minr相当于最大上界,所以每一次区间缩小相当于把某一段区间代表的数变成了定值(maxl对应的区间取l,minr对应的区间取r)。可以建立两个堆来找到最值,关于堆的删除操作:既然每次是确定了一个区间,为什么ql和qr可以独立删除?(为什么maxl删除时不把它对应的r从qr里拿走?)先考虑maxl,它对应的r留在堆里会影响答案的情况只有在maxl <= minr之前把它取出,第一次maxl已经被删了,它对应的r比它大,那么在未来取出这个r作为最小值的时候贡献一定为0,把这个r删掉的结果是取出来更大的r,贡献还是0,只要还有比这个r小的存在它就不会被取出,就不会影响答案。
记录答案就是当前区间上界-下界加上内部的数同时与上下界的差值,设sz为区间内包括的x的数目,ans += (l-r)*(sz+1)
code
//正青春的年华,就是应该献给直指星辰的梦想啊!
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 3e6 + 3;
const ll mod = 998244353;
priority_queue<int> ql;
priority_queue<int, vector<int>, greater<int> > qr;
inline int read()
{
int x = 0, f = 1;
char ch = getchar();
while(ch < '0' || ch > '9')
{
if(ch == '-')
{
f = -1;
}
ch = getchar();
}
while(ch >= '0' && ch <= '9')
{
x = (x << 1) + (x << 3) + (ch ^ 48);
ch = getchar();
}
return x * f;
}
int main()
{
int n = read();
for(int i=1; i<=n; i++)
{
ql.push(read()); qr.push(read());
}
ll ans = 0; int l = 1e8, r = 0;
for(int i=1; i<n; i++)
{
l = min(l, ql.top()); ql.pop();
r = max(r, qr.top()); qr.pop();
if(l <= r) break;
ans += 1ll * (l-r) * (n-i*2+1);
}
printf("%lld\n", ans);
return 0;
}
B. 尽梨了
全场切了!?
再给我100年青春岁月可能才有水平切这种T……
prei表示r <= i的位置的并,得到这个并的这些行必然选0,那么1一定来源与列;nxti表示r > i的位置的并,得到这个并的这些行必然选1,那么0一定来源于列。
//在prei中为1表示强制取1,为0没有限制
//在nxti中为0表示强制取0,为1没有限制
//所以nxti中为1prei才可以为1但不一定是1,nxti中为0prei一定不能选
以上解释pre[i] | nxt[i] 结果是nxt[i].
谐音不错——我尽力了***
code
//正青春的年华,就是应该献给直指星辰的梦想啊
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 5005;
const ll mod = 998244353;//我要是再取错mod我就**
int n, fac[maxn], inv[maxn], p2[maxn], ans;
bitset<maxn> s[maxn], pre[maxn], nxt[maxn];
vector<int> v[maxn];
char c[maxn];
inline int read()
{
int x = 0, f = 1;
char ch = getchar();
while(ch < '0' || ch > '9')
{
if(ch == '-')
{
f = -1;
}
ch = getchar();
}
while(ch >= '0' && ch <= '9')
{
x = (x << 1) + (x << 3) + (ch ^ 48);
ch = getchar();
}
return x * f;
}
ll qpow(ll a, ll b)
{
ll ans = 1;
while(b)
{
if(b & 1) ans = ans * a % mod;
a = a * a % mod;
b >>= 1;
}
return ans;
}
int C(int n, int m)
{
if(n < m || m < 0) return 0;
return 1ll*fac[n]*inv[m]%mod*inv[n-m]%mod;
}
int main()
{
n = read();
for(int i=1; i<=n; i++)
{
scanf("%s", c+1);
//预处理每一行1的个数
for(int j=1; j<=n; j++) if(c[j] == '1') s[i].set(j);
v[s[i].count()].push_back(i);
}
inv[0] = fac[0] = 1; for(int i=1; i<=n; i++) fac[i] = 1ll*fac[i-1]*i%mod;
inv[n] = qpow(fac[n], mod-2); for(int i=n-1; i>0; i--) inv[i] = 1ll*inv[i+1]*(i+1)%mod;
p2[0] = 1; for(int i=1; i<=n; i++) p2[i] = (p2[i-1]+p2[i-1])%mod;
for(int i=1; i<=n; i++)
{
pre[i] = pre[i-1];
for(int x : v[i]) pre[i] |= s[x];//r<=i的1的位置的并
}
for(int i=1; i<=n; i++) nxt[n+1].set(i);
for(int i=n; i>=1; i--)
{
nxt[i] = nxt[i+1];
for(int x : v[i]) nxt[i] &= s[x];//r>=i的0的位置的并
}
//枚举要求i列为1
for(int i=0; i<=n; i++)
{
//在prei中为1表示强制取1,为0没有限制
//在nxti中为0表示强制取0,为1没有限制
//所以nxti中为1prei才可以为1但不一定是1,nxti中为0prei一定不能选1
if((pre[i] | nxt[i]) == nxt[i])
{
ans += p2[v[i].size()]-1; ans %= mod;//减掉2^0,只有1种情况的加一次就够了
//合并为乘法之后可以省略这种讨论,上面是有相等的情况
int c1 = pre[i].count(), c2 = nxt[i].count();
//没有r=a的情况,所有的都被固定了,i-c1=0
ans += C(c2-c1, i-c1); ans %= mod;
//如果没有相等的,n-c1-(n-c2)是没有被确定的位置,c1个必选1,现在有i个1
//剩下的1可以在没有规定必选0的位置和已经填上来满足c1的位置随便填
}
}
printf("%d\n", ans);
return 0;
}
C. 团不过
用总方案数减掉异或和为0的方案数得到答案。
得到异或和为0的方案数:无论i-1异或的结果是什么,都能加入一个i使异或结果为0,有两种不合法的情况:前i-1堆异或为0,由于i不能选0,要减掉;新加入一对石子和之前的某堆重复了,不用考虑到底是和谁重复,两个相同的数异或为0,i和i-1中的任意一堆相同,必然导致剩下的i-2堆异或为0,再乘上第i-1堆可选的值域,前面是和i-1中的哪一堆有重复。
code
//正青春的年华,就是应该献给直指星辰的梦想啊!
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1e7 + 3;
const ll mod = 1e9 + 7;
ll n, p[maxn], p2[maxn], f[maxn];
inline int read()
{
int x = 0, f = 1;
char ch = getchar();
while(ch < '0' || ch > '9')
{
if(ch == '-')
{
f = -1;
}
ch = getchar();
}
while(ch >= '0' && ch <= '9')
{
x = (x << 1) + (x << 3) + (ch ^ 48);
ch = getchar();
}
return x * f;
}
int main()
{
n = read();
p2[0] = 1; for(int i=1; i<=n; i++) p2[i] = p2[i-1]*2%mod;
p[0] = 1; for(int i=1; i<=n; i++) p[i] = p[i-1]*(p2[n]-i)%mod;//总数,就是个全排列
for(int i=3; i<=n; i++)
{
f[i] = (p[i-1]-f[i-1]-(i-1)*f[i-2]%mod*(p2[n]-i+1)%mod+2*mod)%mod;
}
printf("%lld\n", (p[n]-f[n]+mod)%mod);
return 0;
}
D. 七负我
一个不属于完全图中的点如果被删掉不影响答案,所以就是统计最大完全子图的大小,然后平均分配。
好像平分和最大完全子图都由"完全不归纳法"得出???
求完第二部分的f不要跑,记得把子图中的大小也加上,因为统计的时候用的是最大值。
code
//正青春的年华,就是应该献给直指星辰的梦想啊
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 50;
const ll mod = 1e9 + 7;
const double inf = 1e12;
#define count __builtin_popcount
ll n, m, m1, m2, all, Max, e[maxn], f[1<<20];
inline int read()
{
int x = 0, f = 1;
char ch = getchar();
while(ch < '0' || ch > '9')
{
if(ch == '-')
{
f = -1;
}
ch = getchar();
}
while(ch >= '0' && ch <= '9')
{
x = (x << 1) + (x << 3) + (ch ^ 48);
ch = getchar();
}
return x * f;
}
int main()
{
n = read(); m = read(); all = read(); m1 = n>>1, m2 = n-m1;
for(int i=1; i<=m; i++)
{
int x = read(), y = read();
e[x] |= 1ll<<y-1; e[y] |= 1ll<<x-1;
}
for(int sta=1; sta<(1ll<<m1); sta++)
{
ll sum = count(sta); if(sum <= Max) continue;
for(int i=1; i<=m1; i++) if((sta>>i-1)&1) if((e[i]&sta)!=(sta^(1ll<<i-1))) goto X;
Max = max(Max, sum); X:;
}
for(int sta=1; sta<(1ll<<m2); sta++)
{
//printf("-----sta---%d\n", sta);
ll sum = count(sta);
for(int i=1; i<=m2; i++)
{
if((sta>>i-1)&1)
{
if(((e[i+m1]>>m1)&sta)!=(sta^(1ll<<i-1)))
{
//printf("%d is illegal!\n", i);
goto Y;
}
}
}
//printf("sta---%d\n", sta);
f[sta] = sum; Max = max(Max, sum); Y:;
}
//for(int i=1; i<=m2; i++) f[1ll<<i-1] = 1;
for(int sta=1; sta<(1ll<<m2); sta++)
{
ll U = (1ll<<m2)-1, sum = f[sta];
for(int i=1; i<=m2; i++) if((sta>>i-1)&1) U &= e[i+m1]>>m1;
for(int i=1; i<=m2; i++)
{
if(((sta>>i-1)&1)^1)
{
int tmp = (U>>i-1)&1;
//tmp == 0但是sum == 1,还是要加上,也就是我这种状态虽然不合法
//但是它包含了多少合法的全都算数
//这里相当于求了一个前缀和
if(f[sta|(1ll<<i-1)] >= sum+tmp) continue;
f[sta|(1ll<<i-1)] = sum + tmp;
}
}
}
for(int sta=1; sta<(1ll<<m1); sta++)
{
ll U = (1<<m2)-1, sum = count(sta);
for(int i=1; i<=m1; i++) if((sta>>i-1)&1) if((e[i]&sta)!=(sta^(1ll<<i-1))) goto Z;
for(int i=1; i<=m1; i++) if((sta>>i-1)&1) U &= e[i]>>m1;
//因为这里留的是第二类的最大可行点数(和第一部分中的点都有连边),应该用前缀和
Max = max(Max, sum+f[U]); Z:;
}
double ans = (1.0*(Max*(Max-1)/2)*(1.0*all)/(1.0*Max)*(1.0*all)/(1.0*Max));
printf("%.6lf\n", ans);
return 0;
}
code--dfs
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 105;
const ll mod = 1e9 + 7;
int n, m, k, Max, s[maxn];
bool a[maxn][maxn];
inline int read()
{
int x = 0, f = 1;
char ch = getchar();
while(ch < '0' || ch > '9')
{
if(ch == '-')
{
f = -1;
}
ch = getchar();
}
while(ch >= '0' && ch <= '9')
{
x = (x << 1) + (x << 3) + (ch ^ 48);
ch = getchar();
}
return x * f;
}
void dfs(int x, int res)
{
if(res == Max)
{
printf("%.6lf", (double)k*k/res*(res-1)/2);
exit(0);
}
if(x == n+1) return;
dfs(x+1, res);
for(int i=1; i<=res; i++) if(!a[x][s[i]]) return;
s[++res] = x;
dfs(x+1, res);
}
int main()
{
n = read(); m = read(); k = read();
for(int i=1; i<=m; i++)
{
int x = read(), y = read();
a[x][y] = a[y][x] = 1;
}
for(int i=n; i>=2; i--) Max = i, dfs(1, 0);
return 0;
}
对结论的证明(完全图):设u,v为互不相连的两点,设su,sv分别为与u,v相连的点权和,那么u,v的总贡献应该是1/2*(su*tu+sv*tv),由于我们可以决定点权的分配,当构造su==sv时可保证u,v其中一点不选也是一种最优解,因此两个不连通的点间总有一点不会被分配权值,最终答案一定会出现在完全图中。
(平均分配)设ti为团中第i个点的权值,s为团大小,则x = 团中每个节点ti求和,ans = ∑u!=v tu*tv = (x^2-∑t=1,s ti^2)/2,由均值不等式得当ti都相等时ans取最大值。
(最大)每个点t = x/k,答案为x^2*k*(k-1)/(2*k^2),它关于k递增