Qbxt 第十场题解报告
T1
给你 \(N\) 个数,现在第 \(i\) 个数会等概率地从 \([l_i, r_i]\) 中的整数中等概率的随机选择一个,求所有数的和的期望。
数据范围
\(N \leq 10^5, 1 \leq l_i \leq r_i \leq 10^9\)
solution
和的期望等于期望的和。
求出每次的期望然后求和就好。
每次的期望等于 \(\frac{r_i + l_i}{2}\)
复杂度 \(O(n)\)
#include<bits/stdc++.h>
using namespace std;
double Ans;
int read() {
int x = 0, f = 1; char c = getchar();
while(c < '0' || c > '9') {if(c == '-') f = -1;c = getchar();}
while(c >= '0' && c <= '9') {x = x * 10 + c - '0';c = getchar();}
return x * f;
}
int N;
int main() {
freopen("da.in", "r", stdin);
freopen("da.out", "w", stdout);
N = read();
for (int i = 1; i <= N; i++) {
int l = read(), r = read();
Ans = Ans + 1.0 * (r + l) / 2.0;
}
printf("%.6lf", Ans);
return 0;
}
T2
众所周知,小葱同学擅长计算,尤其擅长计算组合数,但这个题和组合数没什么关系。
皮克敏宇宙中有 \(N\) 只皮克敏,你可以开若干枪。每次开枪可以选择任意位置任意方向,将该射线上的皮克敏全部杀光。问第一枪最多杀死多少只皮克敏。
对于 \(30\%\) 的数据,\(N\leq 100\)。
对于另外 \(30\%\) 的数据,\(z\) 坐标为 \(0\)。
对于 \(100\%\) 的数据,\(T\leq 10,N\leq 5000\),存在方案十枪可以干掉所有皮克敏,所有点坐标不超过 \(10^9\)。
solution
问题可以等价于找一条直线,使得直线上的皮克敏尽可能的多。
奇怪的条件:存在一个方案可以十枪干掉所有皮克敏
根据抽屉原理:
皮克敏最多的那条直线,至少有 \(\frac{n}{10}\) 个皮克敏
每一次先随机一个点,这个点在所求直线的概率为 \(\frac{1}{10}\)
那么两个点都在这条直线上的概率就是 \(\frac{1}{100}\)。
直接随机这两个点,多随几次就能找到这条直线。
code
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int MAXN = 1e5 + 5;
const double eps = 1e-8;
const int INF = 0x3f3f3f3f3f3f;
int read() {
int x = 0, f = 1; char c = getchar();
while(c < '0' || c > '9') {if(c == '-') f = -1;c = getchar();}
while(c >= '0' && c <= '9') {x = x * 10 + c - '0';c = getchar();}
return x * f;
}
int N, M, T, Ans;
struct node{int x, y, z;}a[MAXN];
struct Node{
double ak, bk, ck;
bool operator < (const Node &rhs)const{
if(ak != rhs.ak) return ak < rhs.ak;
if(bk != rhs.bk) return bk < rhs.bk;
if(ck != rhs.ck) return ck < rhs.ck;
return false;
}
bool operator == (const Node &rhs) const {
return fabs(ak - rhs.ak) <= eps && fabs(bk - rhs.bk) <= eps && fabs(ck - rhs.ck) <= eps;
}
}b[MAXN];
void Calc(int x) {
int sc = 0;
for (int i = 1; i <= N; i++) {
if(i == x) continue;
++sc;
if(a[i].x == a[x].x) b[sc].ak = INF;
else b[sc].ak = 1.0 * abs(a[i].y - a[x].y) / (1.0 * abs(a[i].x - a[x].x));
if(a[i].x == a[x].x) b[sc].bk = INF;
else b[sc].bk = 1.0 * abs(a[i].z - a[x].z) / (1.0 * abs(a[i].x - a[x].x));
if(a[i].y == a[x].y) b[sc].ck = INF;
else b[sc].ck = 1.0 * abs(a[i].z - a[x].z) / (1.0 * abs(a[i].y - a[x].y));
}
sort(b + 1, b + sc + 1);
int cnt = 0;
for (int i = 1; i <= sc + 1; i++) {
if(b[i] == b[i - 1]) cnt++;
else {
Ans = max(Ans, cnt + 1);
cnt = 1;
}
}
}
signed main() {
freopen("cong.in", "r", stdin);
freopen("cong.out", "w", stdout);
srand(time(NULL));
T = read();
while(T--) {
Ans = 0;
N = read();
for (int i = 1; i <= N; i++) a[i].x = read(), a[i].y = read(), a[i].z = read();
for (int i = 1; i <= 66; i++) {
int x = rand() % N + 1;
Calc(x);
}
cout<<Ans<<"\n";
}
return 0;
}
T3
众所周知,小葱同学擅长计算,尤其擅长计算组合数,但这个题和组合数没什么关系。
\(N\)个点\(M\)条边的无向图,点亮第\(i\)条边的代价为\(v_i\)。我们有\(k\)对奇诺比奥和奇诺比可,我们需要点亮一些边,使得每对奇诺比奥和奇诺比可能够通过点亮的边相遇(每一对内部可以相遇即可,不同对之间不用相遇)。问最小代价。
数据范围
对于\(30\%\)的数据,\(N,M\leq20\)。
对于另外\(30\%\)的数据,\(k=1\)。
对于\(100\%\)的数据,\(N,M\leq10^4,k\leq 4,1\leq s\neq e\leq N,1\leq v\leq10^4\),所有奇诺比可和奇诺比奥不会在同一个位置。
solution
斯坦纳树的板子
斯坦纳树:\(N\) 个点 \(M\) 条边,把其中的 \(k\) 个点连起来的最小代价。
\(k \leq 10\)
做法:状压 \(dp\)
设 \(f_{i, S}\) 表示当前的树根为 \(i\) 并且 \(S\) 表示那些点已经在树上。
答案就是 \(\min\{f_{i, 2^{k} - 1}\}\)
转移
1.现在有两个状态 \(S_1, S_2\) 并且满足 \(S_1\) 与 \(S_2\) 没有交集
转移 \(f[i][s_1 + s_2] = f[i][s_1] + f[i][s_2]\)
因为两个集合可以通过 \(i\) 点连起来。
2.换根
\(i -> j\) 这个转移是带环的,直接跑最短路转移。
回到这个题。
现在已经求出有 \(k\) 个点联通的最小代价。
然后取出合法的状态(每对奇诺比奥要么都在,要么都不在)
然后做一个类似背包的玩意,进行合并。
code
/*
斯坦纳树
f[i][k] 表示考虑到 i 这个点,其中 k 个点已经联通的最小代价
*/
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 1e5 + 5;
int read(){
int x = 0, f = 1; char c = getchar();
while(c < '0' || c > '9' ) {if (c == '-') f = -1;c = getchar();}
while(c >= '0' && c <= '9') {x = x * 10 + c - '0';c = getchar();}
return x * f;
}
int n, m, k, s[MAXN], cnt;
struct edge{int v, nxt, w;}e[MAXN << 1];
int E, head[MAXN];
void add_edge(int u, int v, int w) {
e[++E] = (edge){v, head[u], w};
head[u] = E;
}
int f[1 << 8][MAXN], g[1 << 8];
bool vis[MAXN];
queue<int> q;
int main() {
freopen("ming.in","r",stdin);
freopen("ming.out","w",stdout);
n = read(), m = read(), k = read();
for (int i = 1; i <= k; i++) {
int p = read();
s[p] = 1 << cnt;
cnt++;
p = read();
s[p] = 1 << cnt;
cnt++;
}
for (int i = 1; i <= m; i++) {
int u = read(), v = read(), w = read();
add_edge(u, v, w), add_edge(v, u, w);
}
memset(f, 0x3f, sizeof f);
for (int i = 1; i <= n; i++) f[s[i]][i] = 0;
int M = 1 << cnt, inf = f[0][0];
for (int s = 1; s < M; s++) {
for (int sp = s; sp; sp = (sp - 1) & s) {
for (int i = 1; i <= n; i++)
f[s][i] = min(f[s][i], f[sp][i] + f[s - sp][i]);
}
for (int i = 1; i <= n; i++) if(f[s][i] != inf) q.push(i), vis[i] = 1;
while(!q.empty()) {
int u = q.front(); q.pop();
vis[u] = 0;
for (int j = head[u]; j; j = e[j].nxt) {
int v = e[j].v;
if(f[s][v] > f[s][u] + e[j].w) {
f[s][v] = f[s][u] + e[j].w;
if(!vis[v]) q.push(v), vis[v] = 1;
}
}
}
}
for (int s = 0; s < M; s++) {
bool fag = 0;
for (int i = 0; i < k; i++) {
bool ta_1 = ((s >> (i << 1)) & 1), ta_2 = ((s >> (i << 1 | 1)) & 1);
if(ta_1 != ta_2) {fag = 1; break;}
}
g[s] = inf;
if(!fag) {
for (int i = 1; i <= n; i++) g[s] = min(g[s], f[s][i]);
}
}
for (int s = 0; s < M; s++) {
for (int sp = s; sp; sp = (sp - 1) & s) {
g[s] = min(g[s], g[sp] + g[s - sp]);
}
}
cout<<g[M - 1];
return 0;
}
T4
题目描述
众所周知,小葱同学擅长计算,尤其擅长计算组合数,但这个题和组合数没什么关系。
现在并不是所有的皮克敏都是大小为\(1\times 1\times 1\)的正方体了,有些皮克敏的大小是\(1\times 1\times 2\)的(竖着的方向高度为\(2\),不能把皮克敏横着摆放)。现在我们要把所有皮克敏摆成\(N\)列(可以有列为空),每只皮克敏都有自己的颜色(但是所有大小为\(1\times 1\times 2\)的皮克敏颜色都为\(1\),大小为\(1\times 1\times 1\)的皮克敏颜色都不为\(1\))。现在问左视图有多少种不同的可能性(两个左视图不一样,当且仅当某个位置颜色不一样,忽略方块之间的交界线)。可以只使用部分的皮克敏。
数据范围
对于\(30\%\)的数据,\(N=1,M\leq 10\)。
对于另外\(30\%\)的数据,\(N\leq 2\)。
对于\(100\%\)的数据,\(1\leq N\leq 20,1\leq M\leq100\),颜色为\(1\)的皮克敏数量不超过\(20\)只。
solution
坑
zhx的神仙缩进std
#include<cstdio>
#include<cstdlib>
#include<cstring>
using namespace std;
#define inc(a,b) {a+=b;if (a>=mo) a-=mo;}
#define height (a+b+c+c)
const int mo=1000000007;
const int maxn=102;
const int maxm=102;
const int max1=21;
int n,m,z[maxn],C[maxn<<1][maxn<<1];
int g[maxn<<1][maxn<<1];
int f[maxm][max1][max1][max1][maxm>>1][2];
//f[a][b][c][d][e][f]
//有a个单方块在左视图
//b个半个颜色1的方块在左视图
//c个整个颜色1的方块在左视图
//至少需要用d个单方块来垫脚
//还需要e*2的体积来垫脚
//f代表最上面是不是黑色
//的方案数
bool check(int a,int b,int c,int d,int e)
{
if (a+(b<<1)+(c<<1)+d+(e<<1) > m+z[1]) return false;//总个数不能超
if (a+d > m-z[1]) return false;
if (b+c > z[1]) return false;
if (b>=n) return false;
if (d>=n) return false;
if (a>=maxm || b>=max1 || c>=max1 || d>=max1 || e>=maxm)
{
printf("%d %d %d %d %d\n",a,b,c,d,e);
}
return true;
}
int main()
{
scanf("%d%d",&n,&m);
int k=0;
for (int a=1;a<=m;a++)
{
int c;
scanf("%d",&c);
z[c]++;
if (c>k) k=c;
}
for (int a=0;a<=m;a++)
{
C[a][0]=1;
for (int b=1;b<=a;b++)
{
C[a][b] = C[a-1][b-1];
inc(C[a][b],C[a-1][b]);
}
}
g[1][0]=1;
for (int a=2;a<=k;a++)
for (int b=0;b<=m;b++)
if (g[a-1][b])
for (int c=0;c<=z[a];c++)
inc(g[a][b+c],1ll * g[a-1][b] * C[b+c][c] % mo);
int ans=0;
f[0][0][0][0][0][0]=1;
for (int a=0;a<=m-z[1];a++)
for (int b=0;b<n;b++)
for (int c=0;b+c<=z[1];c++)
for (int d=0;d<n;d++)
for (int e=0;(a+(b<<1)+(c<<1)+d+(e<<1))<=m+z[1];e++)
for (int ib=0;ib<2;ib++)
if (f[a][b][c][d][e][ib])
{
int v=f[a][b][c][d][e][ib];
if (height) inc(ans,1ll * v * g[k][a]%mo);
if (check(a+1,b,c,d,e)) inc(f[a+1][b][c][d][e][0],v);
if (check(a,b,c+1,d,e)) inc(f[a][b][c+1][d][e][1],v);
if (!height)
{
if (check(a,b+1,c+1,d+1,e)) inc(f[a][b+1][c+1][d+1][e][1],v);
}
else if (!ib)
{
int h = height;
int dp = d+(h-1)%2;
int ep = e+(h-1)/2;
if (check(a,b+1,c,dp,ep)) inc(f[a][b+1][c][dp][ep][1],v);
}
}
printf("%d\n",ans);
return 0;
}