最近遇到的一些奇奇怪怪奇技淫巧

然后我CF上绿了

              ————DaisySunchaser

要多思考。


构造——从哪里入手?

CF交互指南要多注意。

我的最初想法:

当我查看了他使用了冰茶几的代码:

#include<bits/stdc++.h>
#define ll long long
#define N 200005
#define mp make_pair
using namespace std;
int T,n,u[N],v[N],cnt,f[N];
int read()
{int x=0,f=1;char ch=getchar();while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}while(ch>='0'&&ch<='9'){x=x*10+ch-48;ch=getchar();}return x*f;
}
int find(int k)
{if(f[k]==k)return k;return f[k]=find(f[k]);
}
void merge(int a,int b)
{int s1=find(a),s2=find(b);f[s1]=s2;
}
void ask(int a,int b)
{printf("? %d %d\n",a,b);fflush(stdout);
}
int check(int a,int b)
{int s1=find(a),s2=find(b);if(s1==s2) return 1;else return 0;
}
void solve(int a,int b)
{if(a==b||cnt==n-1) return;if(a>b) swap(a,b);if(check(a,b)) return;ask(a,b);int x=read();if(x==a) {merge(a,b);u[++cnt]=a;v[cnt]=b;return;}if(cnt==n-1) return;solve(a,x);if(cnt==n-1) return;solve(b,x);
}
int main()
{srand(time(0));T=read();while(T--){	n=read();cnt=0;for(int i=1;i<=n;i++) f[i]=i;for(int i=1;i<=n;i++){for(int j=i+1;j<=n;j++){solve(i,j);if(cnt==n-1) break;}if(cnt==n-1) break;}printf("! ");for(int i=1;i<=n-1;i++) printf("%d %d ",u[i],v[i]);printf("\n");fflush(stdout);}return 0;
}

我的想法:

如果两个点在同一个集合中,那么他们之间一定不会有边。

算是剪枝+记忆化了。

这个做法,好不好呢?当然好!对不对呢?当然对!是不是唯一做法呢?当然不是!

相信每一个题的多解性和复杂性,可尝试性.....

我想了很久,想出了一个很模糊的概念——“无解性”。

构造,交互.....这个题的前卫性也充分体现了其无解性。

每个问题,无论多难无论多易,都具有误无解性。这是很高深的概念,需要深刻地认识到信息学的本质。为了厚积薄发,我在这里就不解释了。

我想出那样的遍历方式是为了更有章法一些。如果是像他这样,当然,从头到尾地遍历也是很好的。你不用去想什么你随机问两个,虽然都差不多。

像一个迷宫,有很多的岔路,不知道最后有路可以通向终点的是哪一条岔路。

但无论选了哪一条,都要仔细地寻找接下来的出路。


CFD你要用数据结构去维护啊喂

先说这个,D哈,我思路是没问题的.......

就是暴力维护了。

要长脑子。最近有进步了啊!


求最值与判定的转换——二分答案(或之类)

先说这不是我第一次学习二分答案你这个煞笔

那么我为什么要这么说呢?

这是因为我最近看了P9755 [CSP-S 2023] 种树这个使人为之保龄的题目。

这个题真正的核心是

一棵树,一时刻遍历一个点,必须从已遍历的点过去,第$i$个点需要在第$a_i$时刻以前遍历,问$t$时刻能否按规定完成全树遍历

这恐怕是一个很common的idea吧!难以想象以前居然没有人把这个出成题

说到这个怎么做乍一看想不出来,其实也很简单,就按时间限制把所有节点从小到大排序,然后从前往后依次过去即可,如果已经被访问的就跳过,很易证的贪心。

其实像这样的容易想到的idea有很多,网上也有流传一些集锦。

所以,我亲爱的同学,如果你问如何出一道好题

  1. 随便找一个好找但是又不是特别普遍的idea
  2. 使用一些数学之类的技巧对idea进行包装并在处理上增加难度
  3. 如果最后是判定,再来一个二分答案将其转化成求最值——完美的好题。

逆向思维——玄而精妙

来看经典题P3243 [HNOI2015] 菜肴制作

我自以为是的提交记录:

实际上,我们的脑子无比地混乱。欲图真正地想出一个题的正解,何其不易。

这个题,既不是DFS,也不是局部拓扑排序,更不是很多人以为的简单拓扑排序,而是逆向拓扑序

虽然建了反图,但我着实没有想到这个。前面我的思路还要不少的数据对他进行hack才可以知道哪里错了,但看了正解,几乎是一目了然。

题解有很多是错误和意表不清的,但这里可以借用一个题解图:

按照题意,我们应当先搞2再搞3。但是如果是求字典序最小的拓扑序,就会先考虑先入的(5或4),而后入的2或3就算3在前面,对他来说也是ok的,因为后面的位贡献没有前面的位大。这是很普遍的关于高位与地位贡献的贪心。

但是我们的题意不是这么要求的。后面2和3的贡献应当大于前面5或4的贡献。这应该怎么办呢?

倒过来呗。

反过来依然满足简单的关于位与贡献的贪心。把题意也符合了。

至于反过来求拓扑并从大到小,最后倒过来输出的这个事情吧,你看到虽然是没问题的,其实不好想。

一般来说,我们看到拓扑,都没有想过他可以倒过来......而且反图、从大到小、倒序输出这些也是比较复杂的工序。

其实关于这个,第一篇题解就说的比较好(但是单看第一篇题解可能理解不了)(后面的题解也没有好到哪里去)

其实对于任何一个看似有着单向矛盾的问题,想办法逆过来,也许可以解决。就像动脉血管的瓣膜一样,当血液正向流动时非常顺畅,但如果要逆流就会受到阻碍(自以为很形象的比喻)

我思考了很久,最终想到了一个不再模糊的概念——“可逆化”。

任何事物都存在某种层面上的可逆化。对于某些来说,寻找他的可逆是很简单、很漫长的过程,是十分艰难的。但谁知道呢?也许找到了血管的正方向,问题就会迎刃而解。

对于这个问题来说,如果贡献更大的在后面,而要求的又是前大后小贡献的,那么简单的反过来就是了。

当然很多问题不是这么简单的。也许会有许多方向不同的瓣膜,只能去使用类似搜索的方式去真正地寻找。

只是提供一个很珍贵的思路罢了。

最后关于对拍:

如果你要拍的是n<=10~20的,最好拍到10000组以上。

#include<cstdlib>
#include<cstdio>
#include<ctime>
int main(){for(int T=1;T<=10000;T++){system("random.exe");double st=clock();system("bf.exe");double ed=clock();system("sol.exe");if(system("fc out.txt ans.txt")){puts("Wrong Answer");return 0;}else{printf("Accepted,测试点 #%d,用时 %.0lfms\n",T,ed-st);}}
}

以及单独建的一个文件夹、对每个问题单独存储的子文件夹。

学会对拍,很多好处!(比如在凡人面前装逼)


随机化——什么奇奇怪怪奇技淫巧

一直想去学模拟退火来着

在我非常不成熟的时候,对算法有着四种分类:

  1. “暴力”类:通过直接遍历状态空间来找到答案。
  2. “贪心”类:指问题本身有着某种性质、满足某种逻辑关系,并可以利用这种关系来优化暴力去解题。
  3. “DP”类:通过充分利用已遍历状态空间、避免重复遍历空间来优化暴力。
  4. “随机”类:顾名思义,是通过随机化与概率论来实现的。

这四类可能被认为是四大“基本元素”。他们一般不会单独出现,而是以复合的形式。现在来看,一个是觉得不成熟,一个是觉得不完全。不过她已经做得很好了。

我认为前三个可以分成一大类,第四个则自成一派。因为这是两种完全不同的做法——在前三个的认识中,一个问题的状态空间始终在那里,你无论怎么做都无法在不访问完他的情况下求出答案,你能做的只有优化。而第四个则可以通过随机对状态空间进行选择性访问,最后再利用其概率大小来估算正确的可能性。在前三个的下面,“暴力”类和“DP”类又可以归成一小类(通过优化或不优化遍历整个状态空间),“贪心”类的做法则相对独立(利用性质对状态空间本身进行优化)。

“随机化”作为一个遗世而独立的类,一直吸引着我的兴趣。

这天,我看了这个年轻的我用Floyd和DFS写了60分的题P8817 [CSP-S 2022] 假期计划

因为看不懂正解,偶然翻到了一篇indie tj:

我思考良久,终于.....

他这样做,也就把原问题弱化成了:每个点都有编号1234,第一个点只能是编号为1的,第二个点只能是编号为2的.....这样,我就会做了呀!

而$\frac{2}{4^4}$则是因为,答案的那四个点的排列顺序1,2,3,4,每个点有$\frac{1}{4}$的概率随机到正确的编号,一共是$\frac{1}{4} * \frac{1}{4} * \frac{1}{4} * \frac{1}{4} = \frac{1}{4^4}$,加上四个点的顺序正着、反着都是一样的,所以一共是$\frac{2}{4^4}$。

不过随机化,当然是随机地越多,对的可能性也越大。我们应当在不T的情况下尽可能地多去随机。这就需要用到卡时

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int mxn=2505;
vector<int>g[mxn],ng[mxn];
ll n,m,k;
ll a[mxn];
int dist[mxn][mxn];
inline void bfs(int x){queue<int>q;for(;q.size();)q.pop();q.push(x);memset(dist[x],63,sizeof(dist[x]));dist[x][x]=0;for(;q.size();){int u=q.front();q.pop();for(int v:g[u])if(dist[x][v]>dist[x][u]+1){dist[x][v]=dist[x][u]+1;q.push(v);}}
}
int col[mxn];
ll dp[mxn],ans;
inline ll dfs(int x,int d){if(dp[x]!=-1)return dp[x];dp[x]=-5000000000000000000ll;if(d==4){if(dist[1][x]<=k+1)dp[x]=a[x];else dp[x]=-5000000000000000000ll;return dp[x];}for(int y:ng[x])if(col[y]==d+1)dp[x]=max(dp[x],dfs(y,d+1)+a[x]);return dp[x];
}
int main(){clock_t st=clock();ios_base::sync_with_stdio(false);srand(1919810);cin>>n>>m>>k;for(int i=2;i<=n;++i)cin>>a[i];for(int i=1,u,v;i<=m;++i){cin>>u>>v;g[u].push_back(v);g[v].push_back(u);}for(int i=1;i<=n;++i)bfs(i);for(int i=1;i<=n;++i)for(int j=i+1;j<=n;++j)if(dist[i][j]<=k+1){ng[i].push_back(j);ng[j].push_back(i);}for(int ee=0;ee<10000;++ee){if(clock()-st>1.98*CLOCKS_PER_SEC)break;if(rand()%3==1)rand();for(int i=2;i<=n;++i)col[i]=rand()%4+1;memset(dp,-1,sizeof(dp));dfs(1,0);ans=max(ans,dp[1]);}cout<<ans<<endl;return 0;
}

如代码所示,也像对拍一样,我们在程序开始时使用clock()记录下现在的时间;并在每一次将要再次进行随机时判断现在是否超过1980ms(时限2s,CLOCKS_PER_SEC=1000,表示1s内CPU运行的时钟周期数为1000个,相当于1ms一个时钟周期,因此一般说操作系统的单位是毫秒)。这样,我们就最大限度地进行了随机化。

在实测中,若修改为1990ms则有一个点TLE;若直接上1s居然全都过了

由此可见,随机化+卡时是一个相当好的算法(主要是我非常喜欢)。

所以综上来说,遇到一个问题,我们一定要敢于去思考吧。设想一种问题的子情况,如果这种情况可以推到全局,那当然是好的;如果没有办法了,我们也可以通过随机化,把全局都变成这种情况,再来计算概率、卡时。


以上都只是一些小小的技巧。真正的实践过程中,当然可以想着如何套用,但更多地需要随机应变、灵活操作。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/786667.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Qt/C++音视频开发81-采集本地麦克风/本地摄像头带麦克风/桌面采集和麦克风/本地设备和桌面推流

一、前言 随着直播的兴起,采集本地摄像头和麦克风进行直播推流,也是一个刚需,最简单的做法是直接用ffmpeg命令行采集并推流,这种方式简单粗暴,但是不能实时预览画面,而且不方便加上一些特殊要求。之前就已经打通了音视频文件和视频流的采集,那是不是可以简单点的方式就能…

如何缩短微信文章链接长度

有时候,我们想把微信公众号的文章发到其他平台上,这时候就需要复制文章的链接。有时候,我们想把微信公众号的文章发到其他平台上,这时候就需要复制文章的链接。 ‍ 手机端复制方式如下: ​ ‍ 微信对于短网址的优化 以前,微信公众号文章的链接特别长。但在 2016 年末,微…

verilog代码与设计总结

Verilog编码风格及设计建议相比于case语句,casez语句将z态看做不关心,casex语句将z态和x态看做不关心。并且所有case类型语句均没有优先级。 锁存器是组合逻辑产生的,一般没有复位端,所以根据其所存特性,在上电的时候没法确定其初始状态,因此正常情况下要避免使用。 组合…

【Azure Logic App】在逻辑应用中开启或关闭一个工作流是否会对其它工作流产生影响呢?

问题描述 使用标准版的Azure Logic App服务,可以创建多个工作流(workflow),如果在启用/禁用其它的工作流时,是否会对正在运行其它工作流造成影响呢? 问题解答 在实际的测验中,我们得到的答案是:会造成影响!在Disabled/Enabled同一个Logic App中的Workflow时,正在运行的…

开源|一款企业应用定制化开发平台,支持企业OA协同办公类信息化系统的建设和开发

前言 在数字化转型的浪潮中,企业面临着多样化的信息系统建设需求。现有的软件系统往往存在定制化程度低、开发周期长、成-本高等问题。此外,随着企业规模的扩大和业务的复杂化,传统的软件系统难以满足灵活多变的业务需 为了解-决这些痛点,企业需要一款能够快速定制、灵活扩…

暑假集训总结 2024

暑假集训总结 2024考试情况:因为身体原因,只参加了29场,表格中标红的是题没改完的 越往后分越低,改题的量也越少,排名和分跟心电图差不多 分低和改题量少不只是因为题难,也有后来状态越来越差,改题的时候很困的原因 为什么排名和分是这样的,主要是心态和答题策略,做不…

Tesla 开发者 API 指南:通过Http发送命令

前言 特斯拉提供两种与汽车通信的方式。一种是使用 API 通过互联网,另一种是使用 BLE 连接。 特斯拉现在只能接受车辆命令 SDK (vehicle command SDK)方式发送命令,该 SDK 使用 Http-Proxy 服务器将命令转发给车辆。除了验证 oAuth 令牌之外,特斯拉正在转向一种更安全的方式…

SFF806A-ASEMI无人机专用SFF806A

SFF806A-ASEMI无人机专用SFF806A编辑:ll SFF806A-ASEMI无人机专用SFF806A 型号:SFF806A 品牌:ASEMI 封装:ITO-220AB 批号:最新 最大平均正向电流(IF):8A 最大循环峰值反向电压(VRRM):600V 最大正向电压(VF):0.95V~0.90V 工作温度:-65C~175C 反向恢复时间:35ns …

博客建站7 - hexo博客独立服务器如何自动部署?

1. 本网站的系统架构 2. 安装git 3. 配置git用户3.1. 为什么要创建git用户 3.2. 创建git用户 3.3. 设置git用户的密码 3.4. 创建公钥-私钥对 3.5. 服务器配置公钥4. hexo配置自动化部署4.1. 配置Git仓库 4.2. hexo站点配置1. 本网站的系统架构网站示例: sunlogging.com 服务器…

RE入门第二天---RC4算法

一.RC4加密简介 RC4(Rivest Cipher 4)是一种流加密算法,由罗纳德李维斯特(Ron Rivest)在1987年开发。RC4算法的核心思想是利用伪随机数生成器(PRNG)和密钥共同生成一个密钥流,该密钥流与明文进行异或运算得到密文。 在RC4算法中,密钥流由两部分组成:密钥调度算法(KS…

ThreeJS Shader的效果样例雷达图和大气层(二)

一、雷达图实现原理:图中是一个旋转的渐变扇形,可以通过先实现一个扇形、然后再实现一个渐变扇形,最后再将扇形旋转来达到最终效果 1. 实现一个夹角为O的扇形,已X轴正方向为单位向量M,UV点到(0,0)形成向量N,通过M和N的点乘就可以得到一个夹角,然后判断角度小于O就可以了…