JZOJ_1222《Kup》——一道劲题

本文介绍了一种算法,用于解决在给定的N*N矩阵中寻找子矩阵的问题,要求子矩阵内数值之和落在[k, 2*k]区间内。文章详细阐述了解题思路,包括矩阵前缀和的概念及其应用,以及如何通过分治思想确定符合要求的子矩阵。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

先上题目:

description

首先你们得承认今天的题目很短很简洁。。。
然后,你们还得承认接下来这个题目的描述更加简洁!!!
Task:给出一个N*N(1≤N≤2000)的矩阵,还给出一个整数K。要你在给定的矩阵中
求一个子矩阵,这个子矩阵中所有数的和的范围要在[k,2*k] 这个区间。
如果有多个这样的子矩阵,请随便输出一个。

Input

第一行包含两个整数K 和N(1≤K≤10^8,1≤N≤2000)。其意义如题目描述!
接下来有N 行,每行有N 个数,表示题目给出的矩阵。矩阵中的数都是非负数,而且
不大于maxlongint。

Output

输出文件仅包含一行,四个整数,分别是你找出来的矩阵的左上角坐标和右下角坐标。
如果不存在这样的子矩阵,请输出0 0 0 0。

Sample input1

4 3
1 1 1
1 9 1
1 1 1

Sample input2

8 4
1 2 1 3
25 1 2 1
4 20 3 3
3 30 12 2

Sample input3

8 4
12 2 1 3
25 1 2 1
4 20 3 3
3 30 12 2

Sample output1

0 0 0 0

Sample output2

1 2 2 4

Sample output3

1 1 1 1

Data Constraint

对于30%的数据,1≤N≤5
对于60%的数据,1≤N≤60
对于100%的数据1≤N≤2000

Time Limits:

1000 ms

总结:

这道题是今天比赛的最后一题,也是我唯一没有切的题。
有一个非常非常简略的题解,ACfast学长大概太相信我们的能力了。
在一堆苍蝇讨论了半天后,最后的结果是请清华神犇来讲讲,他把锅给我们初二背╮(╯▽╰)╭。

必备常识:

矩阵前缀和:f[i][j]=f[i-1][j]+f[i][j-1]-f[i-1][j-1]+a[i][j]
则a[a-c,b-d]=f[c][d]-f[c][b-1]-f[a-1][d]+f[a-1][b-1]

不要问为什么,自己画个图看。


首先由于元素都是非负数,所以若有a[i][j]>2k,那么我们选的矩阵一定不能包括它。

对于一个不包括>2k点的矩阵,如果它的矩阵和>=k,那么它一定有一个子矩阵的和sum满足k<=sum<=2k,请读者先仔细想想为什么,后面会解释。

现在需要求最大子矩阵(不包括>2k的点),由于元素非负,所以这个矩阵一定是嵌在>2k点和边界之间。

我们可以预处理r[i][j],表示(i,j)这个点向右遇到的第一个>2k的点,如果没有即为n+1。

枚举一个左边界l,利用单调栈维护一个使r[i][l]严格>=的东东,那么如果以当前r[i][l]-1作为右边界,上边界就是栈中下面的那一个i’-1,至于下边界现在还不知道。但是当我们遇到新的一个r[j][l]< r[i][l]时,要把r[i][l]退栈时,就知道了下边界是j-1。

注意:0,n+1要当作>2k的点丢进栈里。

那么我们得到了一个最大的矩阵,当sum>k时,如何找到那个符合条件的子矩阵呢?

要用到分治的思想。

如果sum<=2k,那我们可以愉快地直接输出答案。

如果不,我们把矩阵看成若干行,从边上开始每次减去一行。

1.这一行的和<=k,那我们一直减,总会遇到k<=sum<=2k,因为值不可能一次跳过这个范围。

2.这一行的和>=k,那更好,子矩阵就在这单行里,我们枚举一下列即可得出答案。

这道题就解决了。

Code:

#include<cstdio>
#define ll long long
#define fo(i,x,y) for(ll i=x;i<=y;i++)
#define fd(i,x,y) for(ll i=x;i>=y;i--)
using namespace std;
const ll maxn=2005;
ll k,n,bzans,a[maxn][maxn],s[maxn][maxn],r[maxn][maxn],d[maxn];
ll sum(ll a,ll b,ll x,ll y) {
    return s[x][y]-s[x][b-1]-s[a-1][y]+s[a-1][b-1];
}

void solve2(ll a,ll b,ll x,ll y) {
    fo(e,b,y) {
        ll value=sum(a,b,a,e);
        if(value >= k && value <= 2*k) {
            printf("%lld %lld %lld %lld",a,b,a,e);
            bzans=1;
        }
        if(bzans) return;
    }   
}

void solve(ll a,ll b,ll x,ll y) {
    for(;sum(a,b,x,y) > 2*k && a!=x;) {
        if(sum(x,b,x,y) >= 2*k)
            solve2(x,b,x,y);
        if(bzans) return;
        x--;
    }
    if(a == x)
        solve2(a,b,x,y);
    if(bzans) return;

    printf("%lld %lld %lld %lld",a,b,x,y);
    bzans=1;
}

void pop(ll now,ll l) {
    for(;d[0] > 0 && r[d[d[0]]][l] > r[now][l];) {
        ll a=d[d[0]-1] + 1, b=l, x=now - 1, y=r[d[d[0]]][l] - 1;
        if(sum(a,b,x,y) >= k)
            solve(a,b,x,y);
        if(bzans) return;
        d[0]--;
    }
}

void insert(ll now,ll l) {
    pop(now,l);
    d[++d[0]]=now;
}

int main() {
    freopen("kup.in","r",stdin); freopen("kup.out","w",stdout);
    scanf("%lld %lld", &k, &n);
    fo(i,1,n)
        fo(j,1,n) {
            scanf("%lld", &a[i][j]);
            s[i][j]=s[i-1][j]+s[i][j-1]-s[i-1][j-1]+a[i][j];
        }
    fo(i,1,n) {
        r[i][n+1]=n+1;
        fd(j,n,1)
            if(a[i][j] > 2*k)
                r[i][j]=j; else r[i][j]=r[i][j+1];
    }

    fo(l,1,n) {
        d[0]=1; d[1]=0;
        fo(i,1,n) {
            insert(i,l);
            if(bzans) return 0;
        }
        insert(n+1,l);
        if(bzans) return 0;
    }
    printf("0 0 0 0");
}
/************************************************* 函数名:main.c 功 能 :key 演示程序 时 间 :2019/02/28 作 者 : *************************************************/ //程序思路: //扫描按键并识别按键 //按键去抖用延时,延时采用精确延时函数 #include <stm32f10x.h> #include "delay.h" //延时函数的头文件 #include <mykey.h> #include <adc.h> #include <oled.h> #include "stdio.h" //系统时钟初始化函数 //采用固件库函数方式编程 //pll:选择的倍频数,从2开始, 最大为9 /******************************************************************************* * Function Name : Rcc_Init * Description : RCC配置(使用外部8MHz晶振) * Input : uint32_t,PLL的倍频系数,例如9就是9*8=72M * Output : 无 * Return : 无 *******************************************************************************/ void I2C_Init(void); void Stm32_Clock_Init(u8 pll); float ReadVoltage(void); void DisplayVoltage(float voltage); uint16_t ADC_GetConversionValue(ADC_TypeDef* ADCx); void OLED_DisplayString(uint8_t line, uint8_t *str); void Stm32_Clock_Init(u8 pll); int main(void) { ADC_Init(); I2C_Init(); OLED_Init(); Stm32_Clock_Init(9); //系统时钟设置 delay_init(72); //延时初始化 delay_ms(100); while(1) { float voltage = ReadVoltage(); DisplayVoltage(voltage); delay_ms(1000); // 延迟1秒 } } /* LED PE8--PE15 推挽输出 K1 --k3 PA456 要配成内上拉 kUp PA7 要配成内下拉 */ void OLED_DisplayString(uint8_t line, uint8_t *str) { // 设置显示位置 OLED_SetPosition(0, line); while (*str) { OLED_ShowChar(*str++); } } // 读取ADC值并计算电压 float ReadVoltage(void) { uint16_t adc_value = ADC_GetConversionValue(ADC1); float voltage = (adc_value * 3.3) / 4095.0; // 假设VDD为3.3V return voltage; } // 显示电压值 void DisplayVoltage(float voltage) { char buffer[20]; sprintf(buffer, "电压: %.2f V", voltage); OLED_DisplayString(buffer); // 假设存在这样的函数用于显示字符串 } void Stm32_Clock_Init(u8 pll) { ErrorStatus HSEStartUpStatus; /*将外设RCC寄存器重设为缺省值*/ RCC_DeInit(); /*设置外部高速晶振(HSE)*/ RCC_HSEConfig(RCC_HSE_ON); //RCC_HSE_ON——HSE晶振打开(ON) /*等待HSE起振*/ HSEStartUpStatus = RCC_WaitForHSEStartUp(); if(HSEStartUpStatus == SUCCESS) //SUCCESS:HSE晶振稳定且就绪 { /*设置AHB时钟(HCLK)*/ RCC_HCLKConfig(RCC_SYSCLK_Div1); //RCC_SYSCLK_Div1——AHB时钟= 系统时钟 /* 设置高速AHB时钟(PCLK2)*/ RCC_PCLK2Config(RCC_HCLK_Div1); //RCC_HCLK_Div1——APB2时钟= HCLK /*设置低速AHB时钟(PCLK1)*/ RCC_PCLK1Config(RCC_HCLK_Div2); //RCC_HCLK_Div2——APB1时钟= HCLK / 2 /*设置FLASH存储器延时时钟周期数*/ FLASH_SetLatency(FLASH_ACR_LATENCY_2);//FLASH_Latency_2 2延时周期 /*选择FLASH预取指缓存的模式*/ FLASH_PrefetchBufferCmd(FLASH_PrefetchBuffer_Enable);// 预取指缓存使能 /*设置PLL时钟源及倍频系数*/ //RCC_PLLConfig(RCC_PLLSource_HSE_Div1, RCC_PLLMul_9); // PLL的输入时钟= HSE时钟频率;RCC_PLLMul_9——PLL输入时钟x 9 switch(pll) { case 2: RCC_PLLConfig(RCC_PLLSource_HSE_Div1, RCC_PLLMul_2); break; case 3: RCC_PLLConfig(RCC_PLLSource_HSE_Div1, RCC_PLLMul_3); break; case 4: RCC_PLLConfig(RCC_PLLSource_HSE_Div1, RCC_PLLMul_4); break; case 5: RCC_PLLConfig(RCC_PLLSource_HSE_Div1, RCC_PLLMul_5); break; case 6: RCC_PLLConfig(RCC_PLLSource_HSE_Div1, RCC_PLLMul_6); break; case 7: RCC_PLLConfig(RCC_PLLSource_HSE_Div1, RCC_PLLMul_7); break; case 8: RCC_PLLConfig(RCC_PLLSource_HSE_Div1, RCC_PLLMul_8); break; case 9: RCC_PLLConfig(RCC_PLLSource_HSE_Div1, RCC_PLLMul_9); break; default: RCC_PLLConfig(RCC_PLLSource_HSE_Div1, RCC_PLLMul_2); break; } /*使能PLL */ RCC_PLLCmd(ENABLE); /*检查指定的RCC标志位(PLL准备好标志)设置与否*/ while(RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET) { } /*设置系统时钟(SYSCLK)*/ RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK); //RCC_SYSCLKSource_PLLCLK——选择PLL作为系统时钟 /* PLL返回用作系统时钟的时钟源*/ while(RCC_GetSYSCLKSource() != 0x08) //0x08:PLL作为系统时钟 { } } } 帮我找错,以下是错误报告 *** Using Compiler 'V5.06 update 6 (build 750)', folder: 'F:\Keil_v5\ARM\ARMCC\Bin' compiling main.c... main.c(68): warning: #223-D: function "OLED_SetPosition" declared implicitly OLED_SetPosition(0, line); main.c(70): warning: #223-D: function "OLED_ShowChar" declared implicitly OLED_ShowChar(*str++); main.c(84): error: #167: argument of type "char *" is incompatible with parameter of type "uint8_t" OLED_DisplayString(buffer); // 鍋囪瀛樺湪杩欐牱鐨勫嚱鏁扮敤浜庢樉绀哄瓧绗︿覆 main.c(84): error: #165: too few arguments in function call OLED_DisplayString(buffer); // 鍋囪瀛樺湪杩欐牱鐨勫嚱鏁扮敤浜庢樉绀哄瓧绗︿覆 main.c: 2 warnings, 2 errors "main.c" - 2 Error(s), 2 Warning(s).
07-06
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值