noip52
T1
没啥好说的,打表找规律就能过。
但考场用的时间比较多,一方面找的有些慢,另一方面担心自己正确性有问题,写了对拍也放不下。
下次类似问题应避免发生,担心正确性就写个对拍去看下一道题,过一会儿再回来看。
T2
一开始没搞明白它想干嘛,看样例解释才知道一开始有一个筹码,但还是不太会,就先弃掉了,剩1h后来,拿20pts就跑路了。
正解:
题解做法不太明白,这里是XIN的贪心做法。先解释一下样例。
对于一开始不压筹码,相当于每个都放了 \(\frac{1}{2}\) 个,这样黑手扔球后我还是1个筹码。
现在剩下 \(\{2,1\}\) ,分别压了 \(\frac{2}{3}\) 和 \(\frac{1}{3}\) ,然后黑手控制red少了一个。
剩下\(\{1,1\}\) , 题面上是各压了 \(\frac{1}{3}\) ,但实际上,你各压 \(\frac{2}{3}\) 也是等价的,然后这时黑手扔那个都是一样的。
最后只能扔一个了,全部都压到上面。按照这种操作来,该样例最后结果仍然是 \(\frac{8}{3}\) 。
所以就能猜得到一个结论,对于不同种类的球,幕后黑手每次扔个数最多的那一种,即使球平均,压筹码按比例压一定最优。
用优先队列模拟一下这个过程即可,这样做是 \(O(n\log n)\) ,用桶+指针乱移可做到 \(O(n)\) 。
对于不会的题,要敢想猜敢做,不能轻易弃掉。
T3
考场觉得是个点分治,应该能拿35pts,然而直接ac???,后来被卡到70pts
70pts: 大力点分治。原数据能过,但XIN加强之后只有70pts了。
100pts:
斯特林展开不会。
回来填坑。
其实是原题,crash的文明世界。
做法也是一样的...
设一个点 \(u\) 的答案为 \(ans_{u}\) ,那么则有:
于是现在就要对于每个点 \(u\) ,求出其 \(\sum_{i=1}^{n}\tbinom{dis(i,u)}{j}\) 。
跑个树形dp+换根dp即可。
设 \(dp_{1\;u,i}\) 表示来自 \(u\) 子树的贡献,\(dp_{2\;u,i}\) 表示来自 \(u\) 父亲的贡献。
对于 \(dp_{1\;u,i}\) ,计算 \(u\) 之前其儿子 \(v\) 的答案肯定已经计算过,所以可以通过 \(v\) 及 \(v\) 的子树,得到 \(u\) 的答案,即:
再由组合数的递推公式 \(\tbinom{n}{m}=\tbinom{n-1}{m}+\tbinom{n-1}{m-1}\) ,可以得到:
然后跑个换根dp,最后答案求完和,再除以2即可。
Code
#include<cstdio>
#include<cctype>
#define MAX 1000003
#define re register
#define int64_t long long
const int K = 103;
const int inv = 499122177;
const int mod = 998244353;
namespace some
{
struct stream
{
template<typename type>inline stream &operator >>(type &s)
{
bool w=0; s=0; char ch=getchar();
while(!isdigit(ch)){ w|=ch=='-'; ch=getchar(); }
while(isdigit(ch)){ s=(s<<1)+(s<<3)+(ch^48); ch=getchar(); }
return s=w?-s:s,*this;
}
}cin;
struct graph
{
int next;
int to;
}edge[MAX<<1];
int cnt=1,head[MAX];
auto add = [](int u,int v) -> void { edge[++cnt] = (graph){head[u],v},head[u] = cnt; };
auto fd = [](int a,int b) -> int { return b-mod+a>=0?b-mod+a:a+b; };
auto line = []() { printf("\n"); };
auto begin = []() { printf("begin\n"); }; auto end = []() { printf("end\n"); };
}using namespace some;
namespace OMA
{
int n,k,ans;
int str[K][K],fac[K];
int dp1[MAX][K],dp2[MAX][K]; // val from subtree , val frome father
void dfs_up(int u,int fa)
{
dp1[u][0] = 1;
for(re int i=head[u],v; i; i=edge[i].next)
{
v = edge[i].to;
if(v!=fa)
{
dfs_up(v,u);
dp1[u][0] = fd(dp1[u][0],dp1[v][0]);
for(re int j=1; j<=k; j++)
{ dp1[u][j] = fd(dp1[u][j],fd(dp1[v][j-1],dp1[v][j])); }
}
}
}
void dfs_down(int u,int fa)
{
for(re int i=head[u],v; i; i=edge[i].next)
{
v = edge[i].to;
if(v!=fa)
{
dp2[v][0] = ((fd(dp2[u][0],dp1[u][0])-dp1[v][0])+mod)%mod;
dp2[v][1] = (fd(fd(dp2[u][1],dp2[u][0]),fd(dp1[u][1],dp1[u][0]))-fd(dp1[v][1],2*dp1[v][0]%mod))%mod;
for(re int j=2; j<=k; j++)
{
dp2[v][j] = ((fd(fd(dp2[u][j],dp2[u][j-1]),fd(dp1[u][j],dp1[u][j-1]))-fd(fd(dp1[v][j],2*dp1[v][j-1]%mod),dp1[v][j-2]))%mod+mod)%mod;
}
dfs_down(v,u);
}
}
}
auto main = []() -> signed
{
//freopen("node.in","r",stdin);
cin >> n >> k;
for(re int i=2,u,v; i<=n; i++)
{ cin >> u >> v; add(u,v),add(v,u); }
str[0][0] = fac[0] = 1;
for(re int i=1; i<=k; i++)
{
fac[i] = 1ll*fac[i-1]*i%mod;
for(re int j=1; j<=i; j++)
{ str[i][j] = fd(str[i-1][j-1],1ll*j*str[i-1][j]%mod); }
}
dfs_up(1,0),dfs_down(1,0);
//begin(); for(re int i=1; i<=n; i++) { for(re int j=0; j<=k; j++) { printf("dp(%d %d)=%d ",i,j,dp1[i][j]); } line(); } end();
for(re int i=1; i<=n; i++)
{
//ans = 0;
for(re int j=0; j<=k; j++)
{ ans = fd(ans,1ll*str[k][j]*fac[j]%mod*fd(dp1[i][j],dp2[i][j])%mod); }
}
printf("%lld\n",1ll*ans*inv%mod);
return 0;
};
}
signed main()
{ return OMA::main(); }
T4
考场想的是线段树,发现自己不知道该如何维护对应信息,于是就暴力走人了。
60pts:
动态开点线段树。
对于树的每一层,也就是每一种深度都建一颗动态开点线段树,维护每个点的权值,修改直接暴跳 \(x\) ,对于跳到的每一层区间修改,查询就是单点查询。
因为是动态开点,点的标号不会重复,所以开一维数组即可。
原数据能过跑的比正解还快,但被战神加强数据后只剩60pts了。
100pts:分块+根号分治
正解竟然是分块???,菜了,根本不会做。
看了zero4338的blog改出来的.....%%% 。
先求出dfs序,这样就转换到了区间上的问题。
选择用线段树来维护每个点的权值。
对于线段树的每个节点维护一个 \(add1_{i,j}\) 表示区间内深度 \(\%i=j\) 的深度所增加的权值, 和\(add2_{i}\) 表示区间内能整除 \(x\) 的深度 \(i\) 所增加的权值。
当 \(x\le \sqrt{n}\) 时,用 \(add1\) ,\(x>\sqrt{n}\) 时,用 \(add2\) 。
为了方便 \(add1,add2\) 用 \(map\) 来实现。
此题原数据其氵无比,线段树 \(mid\) 打成 \(l\) 都能过原数据,比随的还氵。
Code
#include<map>
#include<cmath>
#include<cctype>
#include<cstdio>
#include<unordered_map>
#define MAX 300005
#define re register
using std::map;
using std::unordered_map;
using std::pair;
using std::make_pair;
typedef pair<int,int> my;
namespace some
{
struct stream
{
template<typename type>inline stream &operator >>(type &s)
{
bool w=0; s=0; char ch=getchar();
while(!isdigit(ch)){ w|=ch=='-'; ch=getchar(); }
while(isdigit(ch)){ s=(s<<1)+(s<<3)+(ch^48); ch=getchar(); }
return s=w?-s:s,*this;
}
}cin;
struct graph
{
int next;
int to;
}edge[MAX<<1];
int cnt=1,head[MAX];
auto add = [](int u,int v) -> void { edge[++cnt] = (graph){head[u],v},head[u] = cnt; };
auto max = [](int a,int b) -> int { return a>b?a:b; };
auto debug = []() -> void { printf("fuck\n"); };
}using namespace some;
namespace OMA
{
int n,q,len,xam;
int dep[MAX],val[MAX];
int lfn[MAX],rfn[MAX];
void dfs(int u,int fa)
{
lfn[u] = ++cnt;
xam = max(xam,val[cnt] = dep[u] = dep[fa]+1);
for(re int i=head[u],v; i; i=edge[i].next)
{
v = edge[i].to;
if(v!=fa)
{ dfs(v,u); }
}
rfn[u] = cnt;
}
struct Tree
{
map<my,int>add1;
unordered_map<int,int>add2;
unordered_map<int,bool>vis;
}st[MAX<<2];
#define ls(p) p<<1
#define rs(p) p<<1|1
#define mid (l+r>>1)
void modify(int p,int l,int r,int lp,int rp,int x,int y,int z)
{
if(lp<=l&&r<=rp)
{
if(x<=len)
{ st[p].add1[make_pair(x,y)] += z,st[p].vis[x] = 1; }
else
{
for(re int i=y; i<=xam; i+=x)
{ st[p].add2[i] += z; }
}
return ;
}
if(lp<=mid)
{ modify(ls(p),l,mid,lp,rp,x,y,z); }
if(rp>mid)
{ modify(rs(p),mid+1,r,lp,rp,x,y,z); }
}
int query(int p,int l,int r,int pos,int res = 0)
{
if(st[p].add2.find(val[pos])!=st[p].add2.end())
{ res += st[p].add2[val[pos]]; }
//printf("l=%d r=%d res=%d\n",l,r,res);
for(auto it : st[p].vis)
{
my tmp = make_pair(it.first,val[pos]%it.first);
if(st[p].add1.find(tmp)!=st[p].add1.end())
{ res += st[p].add1[tmp]; }
}
//printf("l=%d r=%d res=%d\n",l,r,res);
if(l==r)
{ return res; }
if(pos<=mid)
{ return res+query(ls(p),l,mid,pos); }
else
{ return res+query(rs(p),mid+1,r,pos); }
}
auto main = []() -> signed
{
//freopen("node.in","r",stdin);
//freopen("my.out","w",stdout);
cin >> n >> q; len = sqrt(n);
//printf("%d %d\n",n,q);
for(re int i=1,u,v; i<=n-1; i++)
{ cin >> u >> v; add(u,v),add(v,u); }
cnt = 0; dfs(1,0); //debug();
//for(re int i=1; i<=n; i++)
//{ printf("lfn=%d rfn=%d dep=%d\n",lfn[i],rfn[i],dep[i]); }
for(re int i=1,opt,v,x,y,z; i<=q; i++)
{
//debug();
cin >> opt >> v;
if(opt==1)
{ cin >> x >> y >> z; modify(1,1,n,lfn[v],rfn[v],x,(y+dep[v])%x,z); }
else
{ printf("%d\n",query(1,1,n,lfn[v])); }
}
return 0;
};
}
signed main()
{ return OMA::main(); }
没写分块,因为code奇长无比.....