水博客太快乐了
考场
看到题目异常眼熟。。。
\(u,v,w\)
几位大佬以前做过。。。。
理论上来说我也做过但是没什么映像。。。。
先看 \(T1\) 这不是个简单的二维前缀和??
感觉貌似不能二维前缀和。。。尝试三角形的二维前缀和。。。
貌似也不对。。。先看下一道。。。
\(T2\) 期望 \(dp\) ,显然不会,写了暴力骗分走人。。。
再看 \(T3\) 是一个树形 \(dp\) 设计了一个感觉比较正确的状态,但是不太会转移,于是胡了挺长时间的转移,感觉貌似能过,但是也没时间写对拍了。。。。
分数
挂惨了。。。
\(t1\) \(2pts\) \(+\) \(t2\) \(20pts\) \(+\) \(t3\) \(14pts\) \(=\) \(54pts\)
越考越菜了。。。。
发现 \(t1\) 就是一个简单的二维前缀和。。。
\(t3\) 转移的时候挂了,变成 \(14pts\) 。。。。
题解
A. u
就是简单的二维前缀和。。。。
但是维护等腰直角三角形的前缀和确实第一次见。。。。
将一个三角形扩张成一个从左上角顶点开始,一直到矩阵最下方结束的大三角,将其拆成两个小三角形和一个正方形,正方形显然可以用二维前缀和,三角形也可以。。。
求和时要注意 \(sum_{i,j}=sum_{i-1,j}+sum_{i-1,j-1}-sum{i-2,j-1}\)
记录在代码里。。。。。
code
#include<bits/stdc++.h>
using namespace std;
const int N=1e3+10;
inline int read(){
int f=1, x=0; char ch=getchar();
while(!isdigit(ch)) { if(ch=='-') f-=1; ch=getchar(); }
while(isdigit(ch)) { x=x*10+ch-48; ch=getchar(); }
return f*x;
}
int n, q;
long long a[N][N], b[N][N], ans;
int main(void){
n=read(), q=read();
int x, y, l, s;
while(q--){
x=read(), y=read(), l=read(), s=read();
a[x][y]+=s; a[min(x+l, n+1)][min(y+l, n+1)]-=s;
b[min(x+l, n+1)][y]+=s; b[min(x+l, n+1)][min(y+l, n+1)]-=s;
}
for(int i=1; i<=n; ++i){
for(int j=1; j<=n; ++j){
a[i][j]+=a[i-1][j]+a[i-1][j-1];
if(i>2) a[i][j]-=a[i-2][j-1];
b[i][j]+=b[i-1][j]-b[i-1][j-1]+b[i][j-1];
}
}
for(int i=1; i<=n; ++i) for(int j=1; j<=n; ++j) ans^=a[i][j]-=b[i][j];
printf("%lld\n", ans);
return 0;
}
B. v
考虑状压。。。
用 \(1\) 表示当前位置为白,用 \(0\) 表示当前状态为黑。。。
但是这样会有 \(bug\) , \(001\) 与 \(0001\) 虽然是两种不同状态,转换成一个数后却是相同的,所以考虑在最高位 \(+1\) 变成 \(1001\) 和 \(10001\) ,感觉会变成将来一个很常见的套路,最好记录一下。。
乍一看数据范围貌似不能状压,但是其中显然会有很多状态没有用到,所以可以用 \(hash\) 表或 \(map\) ,有些状态可能会多次出现,因此可以记忆化搜索。。。。
由于每次拿出一个球可以从两个不同的位置拿,且要求最有策略,所以每次把抽两个位置的都算出来,取 \(max\) 即可。。。
code
#include<bits/stdc++.h>
using namespace std;
#define int unsigned int
const int N=40, mod=30000019, M=40000000, NM=6000000;
int n, m;
char ul[N];
struct hash_map{
int head[M], cnt=0;
struct node{
int nxt, t;
double w;
}l[NM];
double &operator[](int st){
int u=st%mod;
for(int i=head[u]; i; i=l[i].nxt)
if(l[i].t==st) return l[i].w;
l[++cnt].t=st; l[cnt].w=-1; l[cnt].nxt=head[u];
head[u]=cnt; return l[cnt].w;
}
}mp;
double dfs(int k, int con){
if(k==n-m) return 0;
if(mp[con]!=-1) return mp[con];
mp[con]=0;
double sum=0;
for(int i=1; i<=(k>>1); ++i){
int ul1=(con>>(i-1))&1, ul2=(con>>(k-i))&1;
int t1=((con>>i)<<(i-1))|(con&((1<<(i-1))-1));
int t2=((con>>(k-i+1))<<(k-i))|(con&((1<<(k-i))-1));
sum+=2*max(dfs(k-1, t1)+ul1, dfs(k-1, t2)+ul2)/k;
}
if(k&1){
int i=(k+1)>>1;
int ul1=(con>>(i-1))&1, t1=((con>>i)<<(i-1))|(con&((1<<(i-1))-1));
sum+=(dfs(k-1, t1)+ul1)/k;
}
return mp[con]=sum;
}
signed main(void){
scanf("%d%d", &n, &m);
scanf("%s", ul+1);
int con=0;
for(int i=1; i<=n; ++i)
if(ul[i]=='W') con|=(1<<(i-1));
con|=(1<<n);
printf("%.10lf\n", dfs(n, con));
return 0;
}
C. w
树形 \(dp\) 。
考虑状态,这个状态很好想。。。
因为要翻转的是边,考虑将边下传到点上。。。
则每个点的状态分别表示 \(f[u][0/1]\) 分别表示它连接它父亲的边是否翻转时,最小路径数与最小路径长度。。。
由于当两条路径同时连到一个点时,这两条路径显然可以合并成一条路径,而在计算时是直接加,因此在输出最终答案时需要将最小路径数除二。。
转移很有趣,设过程量 \(p, q\) 分别表示有偶数个点像当前点连边或奇数点向当前点连边。
每次更新时,令 :
\(p=min(p+f[v][0], q+f[v][1])\)
\(q=min(p+f[v][1], q+f[v][0])\)
最终再根据其父亲节点与当前节点的边的状态更新 \(f\) 数组即可。
这在将来貌似也会成为一个常见的套路,最好记录一下。。。。
code
#include<bits/stdc++.h>
using namespace std;
#define f first
#define s second
const int N=1e5+10, INF=0x3f3f3f3f;
inline int read(){
int f=1, x=0; char ch=getchar();
while(!isdigit(ch)) { if(ch=='-') f=-1; ch=getchar(); }
while(isdigit(ch)) { x=x*10+ch-48; ch=getchar(); }
return f*x;
}
int n;
struct edge{
int t, c;
};
vector<edge> l[N];
pair<int, int > f[N][2];
pair<int, int > operator + (const pair<int, int > &x, const pair<int, int > &y) { return make_pair(x.f+y.f, x.s+y.s); }
void dfs(int u, int fa, int op){
pair<int, int > p=make_pair(0, 0), q=make_pair(INF, INF), a, b;
for(edge v : l[u]){
if(v.t==fa) continue;
dfs(v.t, u, v.c);
a=min(p+f[v.t][0], q+f[v.t][1]);
b=min(p+f[v.t][1], q+f[v.t][0]);
p=a, q=b;
}
if(op==0) f[u][1]=make_pair(INF, INF), f[u][0]=min(p, q+make_pair(1, 0));
else if(op==1) f[u][1]=min(p+make_pair(1, 1), q+make_pair(0, 1)), f[u][0]=make_pair(INF, INF);
else f[u][1]=min(p+make_pair(1, 1), q+make_pair(0, 1)), f[u][0]=min(p, q+make_pair(1, 0));
}
signed main(void){
n=read();
int x, y, c, d;
for(int i=1; i<n; ++i){
x=read(), y=read(), c=read(), d=read();
c=c==d ? 0 : d==2 ? 2 : 1;
l[x].push_back((edge){y, c});
l[y].push_back((edge){x, c});
}
dfs(1, 0, 0);
printf("%d %d\n", f[1][0].f>>1, f[1][0].s);
return 0;
}