NOIP模拟测试17「入阵曲·将军令·星空」
入阵曲
题解
应用了一种美妙移项思想,
我们先考虑在一维上的做法
维护前缀和$(sum[r]-sum[l-1])\%k==0$可以转化为
$sum[r]\% k==sum[l-1]\%k$开个桶维护一下即可
然后拓展到二维上
把两行之间所有行拍扁看作一维上的区间,
我们枚举两行和行之间所有列开个桶维护
$n^2 m$复杂度
for(ll i=1;i<=n;i++)
for(ll j=1;j<=i;j++){
flag[0]=1;
for(ll q=1;q<=m;q++){
t[q]=(sum[i][q]-sum[j-1][q]+k)%k;
cnt+=flag[t[q]];
flag[t[q]]++;
}
for(ll q=1;q<=m;q++)
flag[t[q]]=0;
}
至于桶里flag[0]=1初始化含义
当你其他%==0的矩阵放进去时本身就符合条件,在桶里找配对之前就构成合法矩阵,这样做统计了所有%=0的情况
当然这样也行
for(ll i=1;i<=n;i++)
for(ll j=1;j<=i;j++){
for(ll q=1;q<=m;q++){
t[q]=(sum[i][q]-sum[j-1][q]+k)%k;
cnt+=flag[t[q]];
flag[t[q]]++;
}
cnt+=flag[0];
for(ll q=1;q<=m;q++)
flag[t[q]]=0;
}
将军令
题解
$45\%$算法
计算k==1
和小胖收皇宫类似,
思考dp数组含义
$f[x][0]$表示被父亲管辖 $f[x][1]$表示被自己管辖 $f[x][2]$表示被自己儿子管辖
f数组转移
若x被父亲管辖,那么儿子必须被自己管辖或者被儿子的儿子管辖
$f[x][0]=\sum\limits_{y}^{y\in son}min(f[y][1],f[y][2])$
若x被自己管辖,那么x转移随意
$f[x][1]=\sum\limits_{y}^{y\in son} min(f[y][1],f[y][2],f[y][0])$
若x被儿子管辖,那么儿子可以被自己管辖或者被儿子的儿子管辖
但如果所有儿子的儿子代价都比选自己代价小,我们需要强制选出一个$f[y][1]-f[y][2]$差值最小的更新
代码稍微体会一下
void dp(ll x){
if(!son[x]) {
f[x][1]=1;
f[x][2]=0x7ffffff;
f[x][0]=0;
return ;
}
vis[x]=1;
ll sum=0,sum2=0,sum3=0,pan=0,mix=0x7ffffff;
for(ll i=head[x];i;i=nxt[i]){
ll y=ver[i];
if(vis[y]) continue;
dp(y);
sum+=min(f[y][0],min(f[y][1],f[y][2]));
sum2+=min(f[y][2],f[y][1]);
sum3+=min(f[y][1],f[y][2]);
if(f[y][1]<=f[y][2]) pan=1;
else
mix=min(mix,f[y][1]-f[y][2]);
}
f[x][1]=sum+1;
f[x][0]=sum2;
if(pan==1) f[x][2]=sum3;
else f[x][2]=sum3+mix;
}
$75\%$算法
计算k==2
还是思考dp数组含义
如果还是像上一个那样定义要写死
换一种0表示被自己守,1表示被儿子守(至少选了一个儿子),2被孙子守(至少选了一个孙子),3子孙全被覆盖自己没有,4孙子全被覆盖(儿子可以被覆盖可以不被覆盖)自己没有
还是思考转移
被自己守x转移还是随意$f[x][0]=\sum\limits_{y}^{y\in son} min(f[y][0],f[y][1],f[y][2],f[y][3],f[y][4])$
被儿子守x转移比较复杂
儿子需要自保,因为儿子可以管辖自己兄弟,所以随意选,所以可以选到$f[y][3]$
$f[x][1]=\sum\limits_{y}^{y\in son} min(f[y][0],f[y][1],f[y][2],f[y][3])$
也和k==1类似,显然我们还是要选出来一个最小差值
被自己孙子守,那么自己儿子需要自保,$f[y][1],f[y][2],f[y][0]$都满足条件
子孙全被覆盖自己没有,那么就是儿子孙子全被覆盖,儿子本身也要被覆盖,
$f[y][0]$ $f[y][1]$ $f[y][2]$ (显然$f[y][3]$不行)
孙子全被覆盖,那么就是孙子全被覆盖,那么可以是$f[y][3]$,$f[y][1]$,$f[y][0]$,$f[y][2]$
代码稍微体会一下(这个代码有误,我并不能调出来)
void dp2(ll x){
//1表示被自己守,2表示被儿子,3被孙子,4子孙全有自己无,5孙子全有自己无
if(!son[x]){
f2[x][4]=0x7fffffff;
f2[x][5]=0x7fffffff;
f2[x][1]=1;
f2[x][2]=0;
f2[x][3]=0;
return ;
}
vis[x]=1;
ll sum1=0,sum2=0,sum3=0,sum4=0,sum5=0,pan=0,mix1=0x7fffffff,pan2=0,mix2=0x7ffffff;
for(ll i=head[x];i;i=nxt[i]){
ll y=ver[i];
if(vis[y]) continue;
dp2(y);
sum1+=min(min(f2[y][1],f2[y][2]),min(f2[y][3],min(f2[y][4],f2[y][5])));
sum2+=min(min(f2[y][1],f2[y][2]),min(f2[y][3],f2[y][4]));
if(f2[y][1]<=f2[y][2]&&f2[y][1]<=f2[y][3]&&f2[y][1]<=f2[y][4])
pan=1;
else
mix1=min(mix1,f2[y][1]-min(f2[y][2],min(f2[y][3],f2[y][4])));
sum3+=min(f[y][1],min(f[y][2],f[y][3]));
if(f2[y][2]<=f2[y][1]&&f2[y][2]<=f2[y][3])
pan2=1;
else
mix2=min(mix2,f2[y][2]-min(f2[y][1],f2[y][3]));
sum4+=min(min(f2[y][1],f2[y][2]),f2[y][3]);
sum5+=min(min(f2[y][1],f2[y][2]),min(f2[y][3],f2[y][4]));
}
f2[x][1]=1+sum1;
if(pan==1)
f2[x][2]=sum2;
else f2[x][2]=sum2+mix1;
if(pan2==1)
f2[x][3]=sum3;
else f2[x][3]=sum3+mix2;
f2[x][4]=sum4;
f2[x][5]=sum5;
}
思考怎么优化
dp数组含义再次转变
$f[x][w]$表示$f$中<=w最小的数
例如$f[x][4]$表示$min(f[x][1],f[x][2],f[x][3],f[x][4],f[x][0])$
转移类似
$f[x][0]=\sum\limits_{y}^{y\in son} f[y][4] $$ +1$
$f[x][3]=\sum\limits_{y}^{y\in son} f[y][2] $
$f[x][2]=\sum\limits_{y}^{y\in son} f[y][3] $$+差值$
$f[x][4]=\sum\limits_{y}^{y\in son} f[y][3] $
$f[x][1]=\sum\limits_{y}^{y\in son} f[x][4] $$+差值$
最后1 2 3 4 互相取min具体看代码
再次感受一下
void dp2(ll x){
if(!son[x]){
f2[x][0]=1;
f2[x][1]=f2[x][2]=1;f2[x][3]=f2[x][4]=0;
f2[x][1]=0x7fffffff;
return ;
}
vis[x]=1;
f2[x][0]=1;
f2[x][1]=f2[x][2]=f2[x][3]=f2[x][4]=0;
ll pan=0,mix1=0x7fffffff,pan2=0,mix2=0x7ffffff;
for(ll i=head[x];i;i=nxt[i]){
ll y=ver[i];
if(vis[y]) continue;
dp2(y);
f2[x][0]+=f2[y][4];
f2[x][3]+=f2[y][2];
f2[x][4]+=f2[y][3];
mix1=min(f2[y][0]-f2[y][3],mix1);
mix2=min(f2[y][1]-f2[y][2],mix2);
}
f2[x][1]=f2[x][4]+mix1;
f2[x][2]=min(f2[x][3]+mix2,min(f2[x][0],f2[x][1]));
f2[x][3]=min(f2[x][3],f2[x][2]);
f2[x][4]=min(f2[x][3],f2[x][4]);
// printf("mix1=%lld 2=%lld x=%lld f[][1]=%lld [2]=%lld [3]=%lld [4]=%lld [0]=%lld\n",mix1,mix2,x,f2[x][1],f2[x][2],f2[x][3],f2[x][4],f2[x][0]);
}
$100\%$算法
dp我是打不出来
应该会有别的大神打出来dp100分
那么正解是什么神奇的算法呢?
简单贪心!
啊啊啊啊啊又是贪心,我又没有看出来它是贪心,awsl,我太菜了
步骤分比正解难的多得多
贪心10分钟改完AC,每次找到最深得节点找它的k级父亲
实现不要想复杂,暴力改,暴力跳即可
你会发现你比dp还要快!!!!!!!!!!!!!!!!!!!
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define A 1010101
ll n,k,t,tot=0,sum1=0,sum2=0,sumji=0,sumou=0,skriller=0;
ll head[A],nxt[A],ver[A],deep[A],son[A],f[A][3],fa[A];
ll f2[A][10];
struct node{
ll deep,x;
friend bool operator < (node a,node b){
return a.deep<b.deep;
}
}point[A];
priority_queue<node> q;
bool vis[A];
void add(ll x,ll y){
nxt[++tot]=head[x];
head[x]=tot;
ver[tot]=y;
}
ll read(){
ll f=1,x=0;char c=getchar();
while(!isdigit(c)){
if(c=='-') f=-1;
c=getchar();
}
while(isdigit(c)){
x=x*10+c-'0';
c=getchar();
}
return f*x;
}
void dfs(ll x,ll de){
vis[x]=1;
point[x].deep=de;
point[x].x=x;
q.push(point[x]);
for(ll i=head[x];i;i=nxt[i]){
ll y=ver[i];
if(vis[y]) continue;
dfs(y,de+1);
son[x]++;
fa[y]=x;
}
}
void dp(ll x){
if(!son[x]) {
f[x][1]=1;
f[x][2]=0x7ffffff;
f[x][0]=0;
return ;
}
vis[x]=1;
ll sum=0,sum2=0,sum3=0,pan=0,mix=0x7ffffff;
for(ll i=head[x];i;i=nxt[i]){
ll y=ver[i];
if(vis[y]) continue;
dp(y);
sum+=min(f[y][0],min(f[y][1],f[y][2]));
sum2+=min(f[y][2],f[y][1]);
sum3+=min(f[y][1],f[y][2]);
if(f[y][1]<=f[y][2]) pan=1;
else
mix=min(mix,f[y][1]-f[y][2]);
}
f[x][1]=sum+1;
f[x][0]=sum2;
if(pan==1) f[x][2]=sum3;
else f[x][2]=sum3+mix;
}
ll find(ll x){
ll w=1;
while(w<=k){
w++;
x=fa[x];
}
return x;
}
void dp2(ll x){
if(!son[x]){
f2[x][0]=1;
f2[x][1]=f2[x][2]=1;f2[x][3]=f2[x][4]=0;
f2[x][1]=0x7fffffff;
return ;
}
vis[x]=1;
f2[x][0]=1;
f2[x][1]=f2[x][2]=f2[x][3]=f2[x][4]=0;
ll pan=0,mix1=0x7fffffff,pan2=0,mix2=0x7ffffff;
for(ll i=head[x];i;i=nxt[i]){
ll y=ver[i];
if(vis[y]) continue;
dp2(y);
f2[x][0]+=f2[y][4];
f2[x][3]+=f2[y][2];
f2[x][4]+=f2[y][3];
mix1=min(f2[y][0]-f2[y][3],mix1);
mix2=min(f2[y][1]-f2[y][2],mix2);
}
f2[x][1]=f2[x][4]+mix1;
f2[x][2]=min(f2[x][3]+mix2,min(f2[x][0],f2[x][1]));
f2[x][3]=min(f2[x][3],f2[x][2]);
f2[x][4]=min(f2[x][3],f2[x][4]);
// printf("mix1=%lld 2=%lld x=%lld f[][1]=%lld [2]=%lld [3]=%lld [4]=%lld [0]=%lld\n",mix1,mix2,x,f2[x][1],f2[x][2],f2[x][3],f2[x][4],f2[x][0]);
}
void dfs2(ll x,ll fa,ll de){
vis[x]=1;
if(de==k) return ;
for(ll i=head[x];i;i=nxt[i]){
ll y=ver[i];
if(y==fa) continue;
dfs2(y,x,de+1);
}
}
int main(){
n=read(),k=read(),t=read();
// printf("%lld\n",k);
if(k==0){
for(ll i=1,a,b;i<=n-1;i++){
a=read(),b=read();
}
printf("%lld\n",n);
return 0;
}
/* if(k==1){
for(ll i=1,a,b;i<=n-1;i++){
a=read(),b=read();
add(a,b);
add(b,a);
}
dfs(1,1);
memset(vis,0,sizeof(vis));
dp(1);
printf("%lld\n",min(f[1][1],f[1][0]));
return 0;
}
*/ if(k==2){
memset(f2,0x3f,sizeof(f2));
for(ll i=1,a,b;i<=n-1;i++){
a=read(),b=read();
add(a,b);
add(b,a);
}
dfs(1,1);
memset(vis,0,sizeof(vis));
dp2(1);
printf("%lld\n",f2[1][2]);
return 0;
}
else{
for(ll i=1,a,b;i<=n-1;i++){
a=read(),b=read();
add(a,b);
add(b,a);
}
dfs(1,1);
memset(vis,0,sizeof(vis));
while(!q.empty()){
ll x=q.top().x;
q.pop();
if(vis[x]) continue;
ll f=find(x);
// printf("x=%lld f=%lld\n",x,f);
dfs2(f,0,0);
skriller++;
}
printf("%lld\n",skriller);
}
}
星空
题解
首先如果翻转我们楞翻转一次复杂度最高n那么考虑优化
我们将取反转化为异或
思考$1 xor 1=0$
$0 xor 1=1$
那么取反我们就转化为了$xor 1$
定义差分数组为$b[i]=a[i] xor a[i+1]$
整段区间$xor1$差分(第一次看到异或的差分),我们转化为$l xor1$ $r+1xor1$
我们不可能白白翻转一段全是$1$的
我们翻转至少有$1$个零
翻转两端都有$0$那么就可以看作消去
两端只有一端有$0$那么可以看作移动
那么问题就转化为了如何最少移动消去使所有$0$变为$1$
处理出任意两个点之间消去代价(可以完全背包,把每个操作换成$+ $,$-$ 两个代价)
for(ll i=1;i<=m;i++)
for(ll j=a[i];j<=n;j++)
d[j]=min(d[j-a[i]]+1,d[j]); for(ll i=1;i<=m;i++)
for(ll j=n-a[i];j;j--)
d[j]=min(d[j+a[i]]+1,d[j]);
考虑k很小,然后状压解决把所有点消去代价
memset(f,0x7f,sizeof(f));
f[0]=0;
for(ll i=0;i<ci[cnt];i++){
for(ll j=0;j<cnt;j++){
if(!(ci[j]&i)){ for(ll k=j+1;k<cnt;k++){
if((!(ci[k]&i))){
if(f[i]>100000000) continue;
else {
// printf(" i=%lld j=%lld k=%lld cij=%lld cik=%lld f=%lld\n",i,j,k,ci[j],ci[k],f[i]);
f[i|ci[j]|ci[k]]=min(f[i|ci[j]|ci[k]],f[i]+d[abs(pos[j]-pos[k])]);
// printf("f=%lld d=%lld\n",f[i],d[abs(pos[j]-pos[k])]);
}
}
}
break;
}
}
}
代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define A 111111
ll b[A],d[A],Xor[A],pos[A],ci[A],f[A],a[A];
ll cnt,m,n,k;
inline void init()
{
memset(d,0x3f,sizeof(d));
d[0]=0;
for(ll i=1;i<=m;i++)
for(ll j=a[i];j<=n;j++)
d[j]=min(d[j-a[i]]+1,d[j]); for(ll i=1;i<=m;i++)
for(ll j=n-a[i];j;j--)
d[j]=min(d[j+a[i]]+1,d[j]);
for(ll i=1;i<=n;i++){
if(Xor[i])
pos[cnt++]=i;
}
/* for(ll i=1;i<=n;i++){
printf("%lld\n",d[i]);
}
printf("cnt=%lld\n",cnt);
*/}
inline void dp(){
memset(f,0x7f,sizeof(f));
f[0]=0;
for(ll i=0;i<ci[cnt];i++){
for(ll j=0;j<cnt;j++){
if(!(ci[j]&i)){ for(ll k=j+1;k<cnt;k++){
if((!(ci[k]&i))){
if(f[i]>100000000) continue;
else {
// printf(" i=%lld j=%lld k=%lld cij=%lld cik=%lld f=%lld\n",i,j,k,ci[j],ci[k],f[i]);
f[i|ci[j]|ci[k]]=min(f[i|ci[j]|ci[k]],f[i]+d[abs(pos[j]-pos[k])]);
// printf("f=%lld d=%lld\n",f[i],d[abs(pos[j]-pos[k])]);
}
}
}
break;
}
}
}
}
int main(){
ci[0]=1;
for(ll i=1;i<=30;i++)
ci[i]=ci[i-1]<<1/*,printf("ci[%lld]=%lld\n",i,ci[i])*/;
scanf("%lld%lld%lld",&n,&k,&m);
n++;
for(ll i=1,ak;i<=k;i++){
scanf("%lld",&ak);
b[ak]^=1;
}
for(ll i=1;i<=n;i++){
Xor[i]=b[i-1]^b[i];
}
for(ll i=1;i<=m;i++){
scanf("%lld",&a[i]);
}
init();
dp();
printf("%lld\n",f[ci[cnt]-1]);
}
NOIP模拟测试17「入阵曲·将军令·星空」的更多相关文章
- NOIP模拟测试14「旋转子段·走格子·柱状图」
旋转子段 连60分都没想,考试一直肝t3,t2,没想到t1最简单 我一直以为t1很难,看了题解发现也就那样 题解 性质1 一个包含a[i]旋转区间值域范围最多为min(a[i],i)----max(a ...
- NOIP模拟测试5「星际旅行·砍树·超级树」
星际旅行 0分 瞬间爆炸. 考试的时候觉得这个题怎么这么难, 打个dp,可以被儿子贡献,可以被父亲贡献,还有自环,叶子节点连边可以贡献,非叶子也可以贡献,自环可以跑一回,自环可以跑两回, 关键是同一子 ...
- NOIP模拟测试17&18
NOIP模拟测试17&18 17-T1 给定一个序列,选取其中一个闭区间,使得其中每个元素可以在重新排列后成为一个等比数列的子序列,问区间最长是? 特判比值为1的情况,预处理比值2~1000的 ...
- 8.11 NOIP模拟测试17 入阵曲+将军令+星空
T1 入阵曲 前缀和维护可以得60分 f[x1][y1][x2][y2]=sum[x2][y2]-sum[x1-1][y2]-sum[x2][y1-1]+sum[x1-1][y1-1]; O(n4) ...
- NOIP模拟测试19「count·dinner·chess」
反思: 我考得最炸的一次 怎么说呢?简单的两个题0分,稍难(我还不敢说难,肯定又有人喷我)42分 前10分钟看T1,不会,觉得不可做,完全不可做,把它跳了 最后10分钟看T1,发现一个有点用的性质,仍 ...
- NOIP模拟测试21「折纸·不等式」
折纸 题解 考试时无限接近正解,然而最终也只是接近而已了 考虑模拟会爆炸,拿手折纸条试一试,很简单 考你动手能力 代码 #include<bits/stdc++.h> using name ...
- NOIP模拟测试13「矩阵游戏·跳房子·优美序列」
矩阵游戏 考试时思路一度和正解一样,考试到最后还是打了80分思路,结果80分打炸了只得了40分暴力分 题解 算出来第一列的总值,每次通过加每两列之间的差值得出下一列的总值 算第一列我们只需要让当前点* ...
- NOIP模拟测试11「string·matrix·big」
打的big出了点小问题,maxx初值我设的0然后少了10分 第二题暴力打炸 第一题剪了一些没用的枝依然40分 总分70 这是一次失败的考试 string 想到和序列那个题很像,但我没做序列,考场回忆学 ...
- NOIP模拟测试17
T1:入阵曲 题目大意:给定一个N*M的矩形,问一共有多少个子矩形,使得矩形内所有书的和为k的倍数. 60%:N,M<=80 枚举矩形的左上角和右下角,用二维前缀和求出数字之和. 时间复杂度$O ...
随机推荐
- linux 发送邮件
参考资料:https://www.cnblogs.com/imweihao/p/7250500.html https://blog.csdn.net/liang19890820/article/det ...
- 微服务架构开发电商系统需要用Redis、ES和MQ吗?
如果不用什么很高大上的东西,就是有多个微服务就行这种技术架构会很难吗? 我看了一些视频,他们都用到了es.mq.redis的东西,我想不用这些东西,就简单的有多个服务,这样可行吗? 01 使用微服务你 ...
- BUAA软件工程热身作业
写在前面 项目 内容 所属课程 2020春季计算机学院软件工程(罗杰 任健) (北航) 作业要求 热身作业(阅读) 课程目标 培养软件开发能力 本作业对实现目标的具体作用 深入认识自己,总结过往并展望 ...
- python 键盘中断子线程及graceful exiting方案
最近需要实现一个服务程序的graceful exiting,保证在退出前关闭所有已创建的子线程 python借助KeyboardInterrupted异常响应键盘中断,因此首先尝试在子线程中try-c ...
- 百万级数据mysql查询优化
一.limit越往后越慢的原因 当我们使用limit来对数据进行分页操作的时,会发现:查看前几页的时候,发现速度非常快,比如 limit 200,25,瞬间就出来了.但是越往后,速度就越慢,特别是百万 ...
- systemctl list-unit-files
[CentOS]centos7上查看服务开机启动列表 systemctl list-unit-files centos7上查看服务开机启动列表 命令: systemctl list-unit-file ...
- nginx官方源安装-主配置文件详解
HTTP相关术语 PV : Page Visit 页面独立浏览量,查看日志生成条数可以看到PV数量. PV全称Page View,中文翻译即页面浏览.其具体的度量方法是从浏览器发出一个对网络服务器的请 ...
- shell基础之后台运行脚本
使shell脚本后台执行,基本的方法有两种,第一种为在脚本后面追加&符号,第二种为在脚本前面使用nohup命令,结尾再追加&符号 一.后台运行脚本1 1.执行脚本test.sh:./t ...
- gcc 编译过程详解-(转自CarpenterLee)
前言 C语言程序从源代码到二进制行程序都经历了那些过程?本文以Linux下C语言的编译过程为例,讲解C语言程序的编译过程. 编写hello world C程序: // hello.c #include ...
- 安卓开发(2)—— Kotlin语言概述
安卓开发(2)-- Kotlin语言概述 Android的官方文档都优先采用Kotlin语言了,学它来进行Android开发已经是一种大势所趋了. 这里只讲解部分的语法. 如何运行Kotlin代码 这 ...