10.week1
杭州集训周
Polygon
经典区间dp.
看上去是板子,但是有点东西要注意.
就是负数×负数是正数.
这就意味着答案的可能也是从最小值转移的.
注意这个就是真板子了.
结果是区间dp板子一直写不对然后把这题浪费了
软件安装
是很典的树形背包.
但是这个可能有环.很简单,tarjan一下然后树上背包就对了
以防出事,贴一个 \(O(n^2)\)的树形背包
窗口的星星
扫描线,有点板但不完全是,主要是有不少恶心人的细节和一些tricks.
我们首先考虑如何让一个点被看到(考虑贡献)
x0,y0是指所选矩形位置的右上角
移项一下
发现可以抽象为矩形可以在长宽\(H,W\)的矩形贡献亮度,叠加可以求和,然后找出最大值的建造位置
这样只需要维护一个区间求和,查询最大值就行了.
MEX Queries
你说的对,但是被ODT水过了,甚至不用写离散化.
太强了太强了
Prime queries
继续是ODT水题时间.
我们对于一个颜色段是否是质数,可以先把质数表打出来然后Lower bound看找不到得到一样的值.
直接就过了.
种花
直接乘法原理和前缀和.
看来NOIP是把\(O(n^3)\)放过去了.
但是luogu上要求\(O(n^2)\).
这其实也很简单.
我们只需要对于每个点,处理出他的右连续段的后缀和就是"\(C\)","\(F\)"就是前面的基础上乘上一个竖着连续段的长度.
说来简单,但是还是调了很一会的,有些细节要注意.
贴一段关键代码:
void get_c(){
F(i,1,n)D(j,m-1,1){
if(!a[i][j]||!a[i][j+1])continue;
c[i][j]=c[i][j+1]+1;
}
F(j,1,m)D(i,n,1){
if(!a[i][j])continue;
sc[i][j]=sc[i+1][j]+c[i][j];
}
F(i,1,n-1)
F(j,1,m-1)
if(a[i+1][j])(ans_c+=c[i][j]*sc[i+2][j]%mod)%=mod;
cout<<ans_c*cc<<' ';
}
int ans_f;
void get_f(){
D(i,n-1,1){
F(j,1,m){
if(!a[i+1][j]||!a[i][j])continue;
r[i][j]=r[i+1][j]+1;
}
}
F(j,1,m){
D(i,n-1,1){
if(!a[i][j])continue;
sr[i][j]=sr[i+1][j]+r[i][j]*c[i][j];
}
}
F(i,1,n)
F(j,1,m)
if(a[i+1][j])(ans_f+=c[i][j]*sr[i+2][j]%mod)%=mod;
cout<<ans_f*ff<<'\n';
}
希望今年的T1不要让我调吧,可恶.
报数
无语题,直接类似埃筛,对于所有直接暴力筛去就行了,复杂度很宽绰.
数列
有点极端的DP题.
要意识到,这里的二进制限制涉及到了进位问题.
那么我们就要从低位开始DP.
现在我们直接设状态
刷表转移(还要枚举一位该种放\(t\)个)
答案统计呢?
自然是\(dp[m+1][n][枚举][枚举]\)了,主义这里也要考虑进位上的问题
其实整体看来不算太难?
贴一小段
void Dp(){
dp[0][0][0][0]=1;
F(i,0,m)
F(j,0,n)
F(k,0,K)
F(p,0,n)
F(t,0,n-j)
(dp[i+1][j+t][k+(t+p)%2][(t+p)/2]+=dp[i][j][k][p]*C[n-j][t]%mod*v[i][t]%mod)%=mod;
}
signed main(){
n=rd(),m=rd(),K=rd();
F(i,0,m)vv[i]=rd();
prew();
Dp();
int ans=0;
F(k,0,K)
F(p,0,n)
if(k+ppc(p)<=K)(ans+=dp[m+1][n][k][p])%=mod;
cout<<ans<<'\n';
return 0;
}
排水系统
简单的\(Topo\)套上一个分数类.
我们考虑到这个分母完全没有爆__int128,我们就利用这个来实现分数类.
只要注意时时刻刻除以gcd,就不会爆炸
贴一下简单分数类的实现:
struct Fs{
ll zi,mu;
Fs operator+(const Fs &a){
Fs c;
if(!zi)return a;
if(!a.zi)return (*this);
ll g=__gcd(a.mu,mu);
c.zi=(a.zi*mu+a.mu*zi)/g;
c.mu=a.mu/g*mu;
g=__gcd(c.zi,c.mu);
c.zi/=g;
c.mu/=g;
return c;
}
void out(){
if(!zi){
cout<<0<<'\n';
return ;
}
ll g=__gcd(zi,mu);
zi/=g;
mu/=g;
wrt(zi);printf(" ");wrt(mu);printf("\n");
}
}own[N];
Emiya 家今天的饭
永远没有解决的心魔,终于是杀掉了!
首先是题面有点复杂,相当于问的就是:
- 选择非全0
- 每行(方法)选择一个食材
- 每种食材占选择的总餐数的一半以下.

我们发现如果要记录每种食材选多少个来最后判断是不现实的,我们考虑容斥,先算出总方案数然后减去不合法的.
为什么这么想呢?因为不合法的只可能是一种数.
那么我们可以在一切开始之前直接钦定选某个食材为不合法食材,然后开始方案计数.
DP是这样设计状态的:
\(dp_{i,j,k}+=dp_{i-1,j,k}(不选择)\)
\(if(j)dp_{i,j,k}+=dp_{i-1,j-1,k}*a_{i,co}(选非法)\)
\(if(k)dp_{i,j,k}+=dp_{i-1,j,k-1}*(s_i-a_{i,co})(选合法)\)
对着不合法的情况统计答案就是\(de\)了
当然\(all=(\prod_{i=1}^{n}s[i]+1)-1\)
最后就是\(all-de\)
但是这个复杂度还是不对,只有88分.
我们发现其实这个后两维也是可以继续压缩的.
我们不在乎各自选了多少,而且在乎那个差值.
那我们状态直接记录差值,这样就能加速转移了.
对于选择非法,就是\(+1\),选择合法就是\(-1\),我们要的就是所有最后大于0的值.
由于不能出现负数下标,我们选择平移dp数组.
这里贴一段代码:
void Dp(){
F(co,1,m){//谁是限制
memset(dp,0,sizeof(dp));
dp[0][n]=1; //平移了n格
F(i,1,n)
F(j,n-i,n+i){
dp[i][j]+=dp[i-1][j];
if(j)dp[i][j]+=dp[i-1][j-1]*a[i][co];
if(j<=n*2)dp[i][j]+=dp[i-1][j+1]*(s[i]-a[i][co]);
}
F(j,n+1,2*n)de+=dp[n][j];
}
}
现在看来似乎又没有那么难,还是题面太搞人了.

浙公网安备 33010602011771号