0808

快速幂

输入a,b,p;
求a的b次幂对p求余。
把b拆成二进制数,通过位运算得到最低位来巧妙降低复杂度。

int a,b,p;
cin>>a>>b>>p;
int ans = 1;
for(;b;b>>=1)
{
    if(b&1)
        ans = (long long)ans*a%p;
    a = (long long)a*a%p;
}

成对变换

对于非负整数n

  • 若n为偶数,则n^1等于n+1
  • 若n为奇数,则n^1等于n-1

lowbit运算

lowbit(n)定义为非负整数n在二进制表示下“最低位的1及其后边所有的0”构成的数值。

lowbit(n) = n&(-n);

原理:计算机中数值都由补码形式储存,而负数的补码为原码取反加一。假设一个整数0-k-1位都是0,那么他的相反数的补码0-k-1为也都是0,但是第k位为1,k+1之后的与该正数都互为反,所以两者做与运算可以直接求得所求。

奇怪的汉诺塔

三个的汉诺塔的递推公式

f(n) = 2*f(n-1)+1

但是如果是四个(从A到D)的话,就可以先把n个中的前k个放到一个塔上,另外n-k个都是按照三个汉诺塔的来放到第四个(D),然后再将前k个按照四个柱子的方法移到第四个柱子。

d(n) = min(2*d(k)+f(n-k))

其中k < n;

a[1] = 1;
b[1] = 1;
b[0] = 0;
for(int i=2;i<=12;i++)
{
    b[i] = 2*b[i-1]+1;
    a[i] = inf;
    for(int j=1;j<=i;j++)
        a[i] = min(a[i],a[j]*2+b[i-j]);
    
}

前缀和(poj3263)

N头牛,告诉最高的牛高H在p处,然后告诉A可以看到B,可以知道B的高大于等于A的高,A与B之间的牛要比A低

map<pair<int,int>,bool> existed;
int c[10010],d[10010];
int main() 
{
    int n,i,h,r;
    while(cin>>n>>i>>h>>r)
    {
        int a,b;
        for(int i=0;i<r;i++)
        {
            cin>>a>>b;
            if(a>b)swap(a,b);
            if(existed[make_pair(a,b)])continue;
            d[a+1]--;d[b]++;
            existed[make_pair(a,b)] = 1;
        }
        for(int i=1;i<=n;i++)
        {
            c[i] = c[i-1]+d[i];
            printf("%d\n",h+c[i]);
        }
    }
    return 0;
}
  • 输入之后要调整a和b的大小
  • 判重
  • 用另外一个数组记录,然后通过前缀和来表示高度之间的差值(核心)

递归枚举

  • 递归思路是将基本单元缩小

从1-n这n个整数中随机选取任意多个,输出所有可能的方案。

分析

  • 从1-n开始,每个都只有两个选择
  • 对于每两个选择,分别进行后面数字的选择
  • 对于一个数字,做完决策后把痕迹删除
int n;
vector<int> chosen;
void calc(int x)
{
    if(x==n+1)//满足条件即输出
    {
        for(int i=0;i<chosen.size();i++)
        printf("%d ",chosen[i]);
        puts("");//输出完会有回车
        return ;
    }
    calc(x+1);
    chosen.push_back(x);
    calc(x+1);
    chosen.pop_back();//一定要消除痕迹
}
int main() 
{
    while(cin>>n)
        calc(1);
    return 0;
}

递归实现排列型枚举

从1-n 这n(n < 10)个整数排除一行后随机打乱顺序,输出所有可能的次序

  • 先排好n-1,然后对于第n个有选哪个的问题,所以要遍历剩下的
  • 要记录之前选过的,需要一个chosen数组
  • 最后要删除标记
int n;
int order[21];
int chosen[21];
void calc(int k)
{
    if(k==n+1)
    {
        for(int i=1;i<=n;i++)
            printf("%d ",order[i]);
        puts("");
        return;
    }
    for(int i=1;i<=n;i++)
    {
        if(chosen[i])
            continue;
        order[k] = i;
        chosen[i] = 1;
        calc(k+1);
        chosen[i] = 0;
    }
}

int main() 
{
    while(cin>>n)
        calc(1);
    return 0;
}

Fractal Streets(poj3889)

题意不在赘述

递归,从n递归到1,解决最小问题,经过一步一步转化得到n级城市的坐标。

  • 房屋必须要从0开始,因为要方便确定属于哪一个区
  • 在城市升级过程中要格外小心坐标转换
  • 最后计算距离时sqrt的输出
pair<long long,long long> calc(int n,long long m)
{
    if(n==0)
        return make_pair(0,0);
    long long len = 1<<(n-1),cnt = 1<<(2*n-2);
    pair<long long,long long> pos = calc(n-1,m%cnt);
    long long x = pos.first,y = pos.second;
    long long z = m/cnt;
    if(z==0)
        return make_pair(y,x);
    if(z==1)
        return make_pair(x+len,y);
    if(z==2)
        return make_pair(x+len,y+len);
    if(z==3)
        return make_pair(len-y-1,2*len-x-1);
}

int main() 
{
    int t;
    cin>>t;
    while(t--)
    {
        int n;
        long long h,o;
        cin>>n>>h>>o;
        pair<long long,long long>a = calc(n,h-1);
        //cout<<a.first<<a.second<<endl;
        pair<long long,long long>b = calc(n,o-1);
        //cout<<b.first<<b.second<<endl;
        printf("%.0f\n",sqrt((double)(a.first-b.first)*(a.first-b.first)+(double)(a.second-b.second)*(a.second-b.second))*10) ;
        //cout<<dis*10<<endl;
    }
    return 0;
}

总结

  • 今天虽然只看了二十页,有些难题还没有AC,样例过了始终无法AC。
  • 二进制的部分要经常复习
  • 递推和递归条件一开始就要清楚,遇到答案不符的情况可以输出过程量测试找bug
  • 尽量不调试。

转载于:https://www.cnblogs.com/1625--H/p/9446036.html

<think>我们正在处理一个关于ADC0808模数转换器的程序实现问题。用户要求提供C语言代码,用于控制ADC0808进行模数转换,并在数码管上显示结果。根据引用内容,我们了解到ADC0808是一个8位逐次逼近型模数转换器,转换时间约为100微秒,输入电压范围0-5V,使用5V供电。我们将参考引用中提供的程序片段,但注意该片段是不完整的(在引用[1]中程序被截断)。因此,我们需要构建一个完整的程序,同时结合引用[3]中提到的使用AT89C51单片机和12MHz晶振。程序的主要步骤:1.初始化:设置ADC0808的控制信号(如启动转换、输出允许等)以及数码管显示的IO口。2.启动ADC转换:通过控制ADC0808的START和ALE引脚(通常将ALE和START连接在一起,通过一个正脉冲启动转换)。3.等待转换结束:检测EOC(End ofConversion)引脚,当EOC由低变高表示转换完成。4.读取转换结果:使能OE(Output Enable)引脚,从ADC0808的数据端口读取8位数字量。5.将数字量转换为电压值:由于ADC0808是8位,参考电压为5V,因此每个数字量对应的电压为(数字量 *5.0/256)。6.将电压值转换为数码管显示的段码,并驱动数码管显示。注意:在引用[1]中,程序使用了4位数码管,显示缓冲区为dispbuf[4]。我们假设数码管采用动态扫描方式显示。硬件连接假设: -ADC0808的通道0(IN0)连接待测电压(如电位器RV1)。-单片机的P0口连接ADC0808的数据输出(D0-D7)。 -单片机的P2口部分引脚用于控制ADC0808:如P2.0连接START(启动转换),P2.1连接OE(输出使能),P2.2连接EOC(转换结束信号)。-数码管段选接P1口,位选接P3口的低4位(假设4位数码管)。注意:实际硬件连接可能不同,这里根据常见的连接方式假设。用户需要根据实际电路调整。程序代码:我们将编写一个完整的程序,包括主循环、ADC读取函数、数码管显示函数等。注意:由于ADC0808需要时钟,可以使用单片机的ALE信号(在12MHz晶振下,ALE为2MHz)经过分频得到(通常ADC0808的时钟要求为640kHz左右,所以需要分频)。但引用[3]中提到使用12MHz晶振,并能够提供转换时钟。如果使用内部时钟方式,则需要在程序中用软件产生时钟,或者使用外部振荡器。为了简化,我们假设使用单片机ALE信号分频(如通过D触发器分频)得到500kHz左右的时钟。但程序中没有涉及时钟产生,因为时钟通常由硬件电路提供。程序如下:```#include <reg51.h>#include <intrins.h>#define uchar unsignedchar#define uintunsigned int//定义ADC0808控制引脚sbitSTART =P2^0;//启动转换信号(上升沿复位ADC,下降沿启动转换,通常要求至少100ns的脉冲)sbitOE =P2^1;//输出使能(高电平有效)sbitEOC= P2^2;//转换结束信号(转换过程中为低,转换结束变高)//数码管段选码和位选码//假设共阴数码管,段码表(0-9以及小数点,这里假设显示0-9,小数点另外处理,但电压显示通常需要小数点)//注意:实际段码取决于硬件连接,这里使用一个示例段码表(0-F的段码,但电压表通常只显示0-9和点)//下面段码表为共阴数码管,0~9的段码(P1口输出) ucharcode seg_table[] ={0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F,//如果需要显示小数点,则在对应段码上加0x80(因为共阴数码管小数点对应最高位) };//位选控制,4位数码管,位选接P3口的低4位ucharcode bit_table[]= {0xFE,0xFD,0xFB,0xF7//对应第1位到第4位};//显示缓冲区,存放4位数码管要显示的数值(0-9),以及是否显示小数点uchardisp_buf[4];//延时函数,用于数码管动态扫描void delay_ms(uintms){uint i, j;for (i =0; i< ms; i++)for (j =0; j<114;j++); }//读取ADC0808转换结果ucharread_adc(){ucharad_value;//启动转换:START信号先高再低(产生一个下降沿)START =1;_nop_();//短暂延时,确保信号稳定START=0;//等待转换结束(EOC变高)while (EOC==0);//等待EOC变高,表示转换完成//使能输出OE=1;_nop_();//短暂延时,确保数据稳定ad_value= P0;//从P0口读取转换结果OE=0;returnad_value; }//将ADC值转换为电压值(单位:毫伏),并分解为4位数字(例如:5000mV显示为5.000V,但数码管只有4位,所以显示5.000需要4位数码管,包括小数点)//假设我们显示格式为:X.XXX V,但只有4位数码管,因此我们显示为:第一位整数部分,后三位小数部分(但实际只能显示3位小数,所以显示为X.XXX) //注意:ADC值范围0-255,对应0-5V(5000mV)//因此,电压值=(ad_value*5000) /256//然后,将这个电压值分解为千位、百位、十位、个位(单位是毫伏,但显示时我们转换为伏特,即千位是整数部分,百位、十位、个位是小数部分) //例如:5000mV->5.000V,分解为:整数部分5,小数部分000(即百位0,十位0,个位0) //但我们显示时,第一位显示整数部分(5),第二位显示小数点后第一位(0),第三位显示第二位(0),第四位显示第三位(0)voidadc_to_voltage(ucharad_value) {uintvoltage_mv =(uint)(ad_value)*5000/256; //计算电压值(毫伏)//转换为伏特,并分解为4位:例如,5.000V->5,0,0,0//整数部分:voltage_mv /1000//小数部分第一位:(voltage_mv%1000) /100//小数部分第二位:(voltage_mv%100)/10//小数部分第三位:voltage_mv%10disp_buf[0] =voltage_mv /1000;//整数部分(0~5)disp_buf[1] =(voltage_mv %1000)/100;disp_buf[2] =(voltage_mv %100) /10;disp_buf[3]= voltage_mv%10;} //数码管显示函数void display() {uchar i;for (i =0;i <4; i++) {P1 =seg_table[disp_buf[i]]; //输出段码,注意:如果要显示小数点,则在最高位加1(例如,第一位整数部分后面要显示小数点,则第一位显示时加上小数点)//如果是第一位,我们需要显示小数点(因为显示格式为X.XXX,所以第一位后面有小数点)if(i==0){P1=seg_table[disp_buf[i]] |0x80; //加上小数点(共阴数码管,小数点对应段码最高位为1)}else {P1 =seg_table[disp_buf[i]];}P3 =bit_table[i];//位选delay_ms(1);//延时1ms,使数码管稳定显示P1 =0x00;//消隐} }void main() {uchar ad_value;while(1) {ad_value =read_adc();//读取ADC值adc_to_voltage(ad_value);//转换为电压并存入显示缓冲区display();//显示} }```注意:1.上述代码假设了硬件连接方式,如果实际连接不同,需要修改引脚定义。2.数码管段码表(seg_table)和位选表(bit_table)需要根据实际硬件调整(特别是共阴/共阳数码管)。3.在显示函数中,我们给第一位(整数部分)加上了小数点(通过或0x80),这样显示效果为:第一位显示整数部分并带小数点,后面三位显示小数部分。例如,显示“5.000”。4.由于ADC0808的转换时间约为100us,而我们的主循环中不断读取和显示,所以转换速度足够快。5.如果使用其他通道,需要在启动转换前选择通道(通过ADDA、ADDB、ADDC引脚),本例使用通道0(IN0),所以ADDA、ADDB、ADDC都接地。如果使用其他通道,需要设置这三个引脚(通常接单片机的IO口),并在启动转换前设置好。关于通道选择:如果使用多通道,需要设置ADDA、ADDB、ADDC。假设这三个引脚分别接P2.3、P2.4、P2.5,那么选择通道0的代码为:ADDA=0;ADDB =0;ADDC=0;在启动转换前设置即可。由于用户只要求获取ADC0808的程序实现,我们提供了上述代码。但请注意,实际应用中可能需要根据具体电路调整。相关问题:1.如何调整ADC0808的参考电压?2.如果使用多通道采集,程序应如何修改?3.如何提高ADC0808的转换精度?4.数码管显示出现闪烁,如何优化?以上问题可以帮助用户深入理解ADC0808的应用和优化。</think>以下是基于AT89C51单片机的ADC0808模数转换器C语言程序实现,参考了实训报告和数字电压表设计中的关键实现逻辑: ```c #include <reg51.h> #include <intrins.h> #define uchar unsigned char #define uint unsigned int // 定义ADC0808控制引脚 sbit START = P2^0; // 启动转换信号 sbit EOC = P2^1; // 转换结束信号 sbit OE = P2^2; // 输出使能信号 sbit CLK = P2^3; // 时钟信号(需外部提供或软件生成) // 数码管显示相关定义 uchar code seg_table[] = {0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F}; // 0-9段码 uchar disp_buf[4]; // 显示缓冲区 /***** 延时函数 *****/ void delay(uint t) { while(t--); } /***** 产生ADC时钟信号(约500kHz) *****/ void init_timer0() { TMOD |= 0x02; // 定时器0模式2(8位自动重装) TH0 = 0xF4; // 12MHz晶振下250us中断 TL0 = 0xF4; TR0 = 1; // 启动定时器0 ET0 = 1; // 允许定时器0中断 EA = 1; // 开总中断 } /***** 时钟中断服务程序 *****/ void timer0_isr() interrupt 1 { CLK = ~CLK; // 翻转时钟信号 } /***** 读取ADC值 *****/ uchar read_adc() { uchar ad_value; START = 0; // 初始状态 START = 1; // 启动转换(上升沿) _nop_(); START = 0; // 启动转换(下降沿) while(EOC == 0); // 等待转换完成(EOC变高) OE = 1; // 使能输出 ad_value = P1; // 读取转换结果(P1口连接ADC数据线) OE = 0; // 禁用输出 return ad_value; } /***** 电压值转换处理 *****/ void process_voltage(uchar ad_val) { // ADC值转电压(0-5V对应0-255) uint voltage_mv = (uint)ad_val * 5000 / 256; // 转换为毫伏值 // 分解为4位显示数据 disp_buf[0] = voltage_mv / 1000; // 千位(整数部分) disp_buf[1] = voltage_mv % 1000 / 100; // 百位(小数第一位) disp_buf[2] = voltage_mv % 100 / 10; // 十位(小数第二位) disp_buf[3] = voltage_mv % 10; // 个位(小数第三位) } /***** 数码管显示函数 *****/ void display() { uchar i, wei = 0xFE; for(i=0; i<4; i++) { P0 = seg_table[disp_buf[i]]; // 段选输出 if(i == 0) P0 |= 0x80; // 第一位添加小数点 P2 = (P2 & 0xF0) | (wei & 0x0F); // 位选控制(P2低4位) delay(200); wei = (wei << 1) | 0x01; // 移位选择下一位 } } /***** 主函数 *****/ void main() { init_timer0(); // 初始化时钟 while(1) { uchar ad_val = read_adc(); // 读取ADC值 process_voltage(ad_val); // 处理电压值 display(); // 显示电压 } } ``` ### 关键实现说明: 1. **时钟生成**:使用定时器0产生500kHz时钟信号($f=\frac{12\text{MHz}}{2 \times (256-244)} \approx 500\text{kHz}$),满足ADC0808的时钟要求[^2] 2. **控制时序**: - 启动转换:START引脚产生下降沿 - 转换等待:检测EOC引脚变高电平 - 数据读取:使能OE后读取P1口数据 3. **电压转换**: - ADC值范围:$0\sim255$ - 电压计算:$V = \frac{\text{ADC值} \times 5000}{256}$ (单位:mV) 4. **数码管显示**: - 显示格式:X.XXX (伏特) - 动态扫描:4位数码管轮流刷新 > 注意:实际硬件中需确保ADC0808的参考电压$V_{ref}=5\text{V}$,输入电压范围$0\sim5\text{V}$[^3]。数码管连接方式需根据实际电路调整段选和位选端口。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值