zoukankan      html  css  js  c++  java
  • 【转】GT 的性能测试方案解析

     

    前言

    本文将整理腾讯GT各个性能测试项的测试方法,目的是为了帮助移动性能专项测试同学快速过一遍腾讯GT各个性能数据是如何获取的。
    另外对腾讯GT还不了解或者不知道它能做什么的同学可以看看这篇文章:https://testerhome.com/topics/9092

    一.GT性能测试方案之CPU测试

    1.简要流程

    • 初始化cpu的数据
    • 提供了两种方法获取CPU数据 getCpuUsage: 整机的CPU使用水平,主要用于实时刷新GT上的CPU数据。通过读取/proc/stat的数据,将每一个核的cpu使用跟闲置数据提取。使用率永远是增量式计算。计算方法为100*(cpu忙时增量-cpu整体增量),从计算方法来看,可能会导致负数出现。 getProcessCpuUsage:计算进程的CPU使用率,主要通过"/proc/" + pid + "/stat"来计算,在这里回京过一系列计算,拿到进程的CPU时间片

    2.代码流程

    • cpu数据初始化 经过初始化,让CPU整体使用跟进程的CPU占用都为0
    public CpuUtils() {
       initCpuData();
    }
    
    private void initCpuData() {
       pCpu = o_pCpu = 0.0;
       aCpu = o_aCpu = 0.0;
    
    }
    • 通过不同的调用栈,来监控不同的CPU数据
      整体CPU使用率:由于getCpuUsage是通过后台线程不断刷新来实现的,因此,o_cpu/o_idle数据不断在实时更新

      RandomAccessFile reader = null;
      try {
      reader = new RandomAccessFile("/proc/stat", "r");
      String load;
      load = reader.readLine();
      String[] toks = load.split(" ");
      double c_idle = Double.parseDouble(toks[5]);
      double c_cpu = Double.parseDouble(toks[2])
           + Double.parseDouble(toks[3])
           + Double.parseDouble(toks[4])
           + Double.parseDouble(toks[6])
           + Double.parseDouble(toks[8])
           + Double.parseDouble(toks[7]);
      if (0 != ((c_cpu + c_idle) - (o_cpu + o_idle))) {
        // double value = (100.00 * ((c_cpu - o_cpu) ) / ((c_cpu +
        // c_idle) - (o_cpu + o_idle)));
        usage = DoubleUtils.div((100.00 * ((c_cpu - o_cpu))),
              ((c_cpu + c_idle) - (o_cpu + o_idle)), 2);
        // Log.d("CPU", "usage: " + usage);
        if (usage < 0) {
           usage = 0;
        }
        else if (usage > 100)
        {
           usage = 100;
        }
        // BigDecimal b = new BigDecimal(Double.toString(value));
      
        // usage = b.setScale(2,
        // BigDecimal.ROUND_HALF_UP).doubleValue();
        // Log.d("CPU", "usage: " + usage);
      }
      o_cpu = c_cpu;
      o_idle = c_idle;
      } catch (IOException e) {
      e.printStackTrace();
      } finally {
      FileUtil.closeRandomAccessFile(reader);
      }
       
    • 进程的CPU使用时间片获取

    public String getProcessCpuUsage(int pid) {
    
       String result = "";
       String[] result1 = null;
       String[] result2 = null;
       if (pid >= 0) {
    
          result1 = getProcessCpuAction(pid);
          if (null != result1) {
             pCpu = Double.parseDouble(result1[1])
                   + Double.parseDouble(result1[2]);
          }
          result2 = getCpuAction();
          if (null != result2) {
             aCpu = 0.0;
             for (int i = 2; i < result2.length; i++) {
    
                aCpu += Double.parseDouble(result2[i]);
             }
          }
          double usage = 0.0;
          if ((aCpu - o_aCpu) != 0) {
             usage = DoubleUtils.div(((pCpu - o_pCpu) * 100.00),
                   (aCpu - o_aCpu), 2);
             if (usage < 0) {
                usage = 0;
             }
             else if (usage > 100)
             {
                usage = 100;
             }
    
          }
          o_pCpu = pCpu;
          o_aCpu = aCpu;
          result = String.valueOf(usage) + "%";
       }
       p_jif = pCpu;
       return result;
    }
     

    二.GT性能测试方案之内存测试

    1.简要流程

    内存测试主要通过handleMessage来触发,根据msg参数来决定任务执行

    • 内存数据获取:仅仅简单通过dumpsys meminfo来获取内存数据,然后通过解析来得到pss_Native/naticeHeapSize/naticeAllocated/pss_OtherDev/pss_graphics/pss_gl/pss_UnKnown/pss_total数据
    • dumpHeap:通过am dumpheap 来获取heap文件
    • GC:直接kill -10 $pid 来完成GC

    2.测试方法

    • 内存数据获取
    public static MemInfo getMemInfo(String packageName)
    {
       MemInfo result = null;
       String resultString = null;
       try {
          resultString = runCMD("dumpsys meminfo " + packageName);
       } catch (Exception e) {
          e.printStackTrace();
          return MemInfo.EMPTY;
       }
    
       if(Env.API < 14)
       {
          result = parseMemInfoFrom2x(resultString);
       }
       else if (Env.API < 19)
       {
          result = parseMemInfoFrom4x(resultString);
       }
       else
       {
          result = parseMemInfoFrom44(resultString);
       }
    
       return result;
    }
    dumpHeap
    private void dumpHeap() {
       String pid = String.valueOf(ProcessUtils
             .getProcessPID(AUTManager.pkn.toString()));
    
       if (!pid.equals("-1")) {
          boolean isSucess = true;
          ProcessBuilder pb = null;
    
          String sFolder = Env.S_ROOT_DUMP_FOLDER + AUTManager.pkn.toString() + "/";
          File folder = new File(sFolder);
          if (!folder.exists())
          {
             folder.mkdirs();
          }
    
          String cmd = "am dumpheap " + pid + " "// 命令
                + Env.S_ROOT_DUMP_FOLDER + AUTManager.pkn.toString() + "/"// 输出路径
                + "dump_" + pid + "_" + GTUtils.getSaveDate() + ".hprof"; // 输出文件名
          pb = new ProcessBuilder("su", "-c", cmd);
    
          Process exec = null;
    
          pb.redirectErrorStream(true);
          try {
             exec = pb.start();
    
             InputStream is = exec.getInputStream();
             BufferedReader reader = new BufferedReader(
                   new InputStreamReader(is));
    
             while ((reader.readLine()) != null) {
                isSucess = false;
             }
          } catch (Exception e) {
             e.printStackTrace();
             isSucess = false;
          }
          // 至此命令算是执行成功
          if (isSucess)
          {
             handler.sendEmptyMessage(6);
          }
    
       } else {
          Log.d("dump error", "pid not found!");
       }
    }
    GC
    private void gc() {
       String pid = String.valueOf(ProcessUtils
             .getProcessPID(AUTManager.pkn.toString()));
    
       if (!pid.equals("-1")) {
          boolean isSucess = true;
          ProcessBuilder pb = null;
    
          String cmd = "kill -10 " + pid;
          pb = new ProcessBuilder("su", "-c", cmd);
    
          Process exec = null;
    
          pb.redirectErrorStream(true);
          try {
             exec = pb.start();
    
             InputStream is = exec.getInputStream();
             BufferedReader reader = new BufferedReader(
                   new InputStreamReader(is));
    
             while ((reader.readLine()) != null) {
                isSucess = false;
             }
          } catch (Exception e) {
             e.printStackTrace();
             isSucess = false;
          }
          // 至此命令算是执行成功
          if (isSucess)
          {
             handler.sendEmptyMessage(5);
          }
    
       } else {
          Log.d("gc error", "pid not found!");
       }
    }
    View Code

    三.GT性能测试方案之内存填充

    1.简要流程

    内存填充,主要是通过调用系统的malloc来分配内存。内存释放,则是通过系统free来释放。

    2. 代码流程

    • 内存分配以及释放的函数
    const int BASE_SIZE = 1024*1024; // 1M
    
    int fill(int blockNum)
    {
        int memSize = blockNum * BASE_SIZE;
        p = (char *)malloc(memSize);
        int i;
        for (i = 0; i < memSize; i++)
        {
            p[i] = 0;
        }
        return 0;
    }
    
    int freeMem()
    {
        free(p);
        return 0;
    }
     
    • 加载com_tencent_wstt_gt_api_utils_MemFillTool.c,并提供度应对操作接口
    public class MemFillTool {
    
       public MemFillTool() {
       }
    
       public static MemFillTool instance = null;
    
       public static MemFillTool getInstance() {
          if (instance == null) {
             System.loadLibrary("mem_fill_tool");
             instance = new MemFillTool();
          }
          return instance;
       }
    
       // 填充xxxMB内存
       public native int fillMem(int blockNum);
    
       // 释放刚才填充的内存
       public native int freeMem();
    }
     

    四.GT性能测试方案之帧率测试

    1.简要流程

    FPS数据收集是一个定时任务(4.3后1s一次),通过异步线程中不断获取FPS数据来刷新到前端页面。而广播模式调用,则直接从缓存的field中获取数据即可。
    在这里GT获取fps数据,也是通过采用surfaceflinger来获取,但我感觉好像是有问题的。因为,一般surfaceflinger数据获取的命令是adb shell dumpsys SurfaceFlinger --latency <window name>在这里直接定义了把"service call SurfaceFlinger 1013"字符串写到流里,没看明白这个操作跟帧率获取有什么关系。刚去了解了下,Runtime.getRuntime()原来执行多条命令时后续只要拿到processDataOutputStream对象,继续writeBytes就可以保证是在同一个上下文中执行多条命令了。

    2.代码流程

    • 判断当前root状态,如果没有root直接返回,避免消耗系统资源
    if (! GTFrameUtils.isHasSu())
    {
       return;
    }
     
    • 计算一个周期内的帧率数据,由于比较简单(除了在1中surfaceflinger数据存疑外),直接把核心代码发出来
    startTime = System.nanoTime();
    if (testCount == 0) {
       try {
          lastFrameNum = getFrameNum();
       } catch (IOException e) {
          e.printStackTrace();
       }
    }
    int currentFrameNum = 0;
    try {
       currentFrameNum = getFrameNum();
    } catch (IOException e) {
       e.printStackTrace();
    }
    int FPS = currentFrameNum - lastFrameNum;
    if (realCostTime > 0.0F) {
       int fpsResult = (int) (FPS * 1000 / realCostTime);
       defaultClient.setOutPara("FPS", fpsResult);
    }
    lastFrameNum = currentFrameNum;
    
    testCount += 1;
     
    • 帧率获取的部分也发一下。另外感谢@codeskyblue 的指点,service call SurfaceFlinger 1013这个命令是获取系统的总的刷新帧率(返回的是16进制)
    public static synchronized int getFrameNum() throws IOException {
       String frameNumString = "";
       String getFps40 = "service call SurfaceFlinger 1013";
    
       if (process == null)
       {
          process = Runtime.getRuntime().exec("su");
          os = new DataOutputStream(process.getOutputStream());
          ir = new BufferedReader(
                new InputStreamReader(process.getInputStream()));
       }
    
       os.writeBytes(getFps40 + "
    ");
       os.flush();
    
       String str = "";
       int index1 = 0;
       int index2 = 0;
       while ((str = ir.readLine()) != null) {
          if (str.indexOf("(") != -1) {
             index1 = str.indexOf("(");
             index2 = str.indexOf("  ");
    
             frameNumString = str.substring(index1 + 1, index2);
             break;
          }
       }
    
       int frameNum;
       if (!frameNumString.equals("")) {
          frameNum = Integer.parseInt(frameNumString, 16);
       } else {
          frameNum = 0;
       }
       return frameNum;
    }
     

    五.GT性能测试方案之流畅度测试

    1.简要流程

    腾讯的流畅度测试比较简单粗暴,测试方式是通过初始化choreographer日志级别,生成Choreographer日志来得到当前操作的丢帧。通过一系列计算后来计算流畅度。

    2.测试方法

    • 执行setprop debug.choreographer.skipwarning 1
    View.OnClickListener button_write_property = new View.OnClickListener() {
    
            @Override
            public void onClick(View v) {
                String cmd = "setprop debug.choreographer.skipwarning 1";
                ProcessBuilder execBuilder = new ProcessBuilder("su", "-c", cmd);
                execBuilder.redirectErrorStream(true);
                try {
                    execBuilder.start();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        };
     
    • 执行getprop debug.choreographer.skipwarning判断,为1则可以进行测试
    View.OnClickListener button_check_status = new View.OnClickListener() {
    
            @Override
            public void onClick(View v) {
                String cmd = "getprop debug.choreographer.skipwarning";
                ProcessBuilder execBuilder = new ProcessBuilder("sh", "-c", cmd);
                execBuilder.redirectErrorStream(true);
                try {
                    TextView textview = (TextView) findViewById(R.id.textviewInformation);
                    Process p = execBuilder.start();
                    InputStream is = p.getInputStream();
                    InputStreamReader isr = new InputStreamReader(is);
                    BufferedReader br = new BufferedReader(isr);
                    Boolean flag = false;
                    String line;
                    while ((line = br.readLine()) != null) {
                        if (line.compareTo("1") == 0) {
                            flag = true;
                            break;
                        }
                    }
    
                    if (flag) {
                        textview.setText("OK");
                    } else {
                        textview.setText("NOT OK");
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        };
     
    • 执行adb logcat -v time -s Choreographer:I *:S
    • 过滤获取当前pid丢帧值
    protected void onHandleIntent(Intent intent) {
            try {
    
                String str = intent.getStringExtra("pid");
                int pid = Integer.parseInt(str);
    
                List<String> args = new ArrayList<String>(Arrays.asList("logcat", "-v", "time", "Choreographer:I", "*:S"));
    
                dumpLogcatProcess = RuntimeHelper.exec(args);
                reader = new BufferedReader(new InputStreamReader(dumpLogcatProcess.getInputStream()), 8192);
    
                String line;
    
                while ((line = reader.readLine()) != null && !killed) {
    
                    // filter "The application may be doing too much work on its main thread."
                    if (!line.contains("uch work on its main t")) {
                        continue;
                    }
                    int pID = LogLine.newLogLine(line, false).getProcessId();
                    if (pID != pid){
                        continue;
                    }
    
                    line = line.substring(50, line.length() - 71);
                    Integer value = Integer.parseInt(line.trim());
    
                    SMServiceHelper.getInstance().dataQueue.offer(value);
                }
            } catch (IOException e) {
                Log.e(TAG, e.toString() + "unexpected exception");
            } finally {
                killProcess();
            }
        }
     
    • 数据处理得到sm值 腾讯这边的处理方案是:当丢帧<60时,流畅度SM =60-frame; 当丢帧frame>60时,流畅度SM = 60-frame%60。不过这种处理方式是有问题的。在这里要先说下流畅度计算的原理:
      • VSync机制可以通过其Loop来了解当前App最高绘制能力,固定每隔16.6ms执行一次,这样最高的刷新的帧率就控制在60FPS以内,Choreographer日志可以打印当前丢帧数,因此通过计算,得到当前APP的流畅度。
      • 而计算这样来计算可能会更加准确(个人看法,欢迎讨论): SM= 60-丢帧frame/每两行同一线程的丢帧时间差(单位:s),如果只关心UI线程,那就只需要统计UI线程即可。
    while (true) {
        if (pause) {
            break;
        }
        int x = count.getAndSet(0);
        // 卡顿大于60时,要将之前几次SM计数做修正
        if (x > 60) {
            int n = x / 60;
            int v = x % 60;
            TagTimeEntry tte = OpPerfBridge.getProfilerData(key);
            int len = tte.getRecordSize();
            // 补偿参数
            int p = n;
            //Math.min(len, n);
            /*
            * n > len是刚启动测试的情况,日志中的亡灵作祟,这种情况不做补偿;
            * 并且本次也记为60。本逻辑在两次测试间会清理数据的情况生效。
            */
            if (n > len) {
                globalClient.setOutPara(key, 60);
    //          globalClient.setOutPara(SFKey, 0);
            } else {
                for (int i = 0; i < p; i++) {
                TimeEntry te = tte.getRecord(len - 1 - i);
                te.reduce = 0;
                }
            globalClient.setOutPara(key, v);
    //      globalClient.setOutPara(SFKey, 60 - v);
            }
        } else {
            int sm = 60 - x;
            globalClient.setOutPara(key, sm);
    //      globalClient.setOutPara(SFKey, x);
        }
     

    六.GT性能测试方案之流量测试

    1.简要流程

    流量测试有三种方案,默认采用方案1

    • 通过读取"/proc/uid_stat/" + uid + "/tcp_snd"获取发送跟接收流量
    • 直接调用android的api:TrafficStats.getUidTxBytes(uid)来获取流量数据(该方法号称是获取到指定 uid 发送流量的总和,但实测情况是只有 tcp 层的流量)
    • 第三种方案居然空在那里,那实际上只有两种方案

    2.代码流程

    • 初始化流量
    public void initProcessNetValue(String pName) {
    
       p_t_base = getOutOctets(pName);
       p_r_base = getInOctets(pName);
    
       p_t_add = 0;
       p_r_add = 0;
    }
     

    其中getOutOctets/getInOctets具体对应什么方法,需要看设备是不是支持uid流量数据获取

    • 获取增加的流量
    public String getProcessNetValue(String pName) {
       StringBuffer sb = new StringBuffer();
    
       java.text.DecimalFormat df = new java.text.DecimalFormat("#.##");
       p_t_cur = getOutOctets(pName);
       p_r_cur = getInOctets(pName);
       p_t_add = (p_t_cur - p_t_base) / B2K;
       p_r_add = (p_r_cur - p_r_base) / B2K;
    
       sb.append("t");
       sb.append(df.format(p_t_add));
       sb.append("KB|r");
       sb.append(df.format(p_r_add));
       sb.append("KB");
    
       return sb.toString();
    }
     
    • 矫正处理
     
    // modify on 20120616 过滤有的手机进程流量偶尔输出负数的情况
    if ((nowT != lastT || nowR != lastR) && nowT >= 0 && nowR >= 0) {
       OpPerfBridge.addHistory(op, value, new long[]{(long) nowT, (long) nowR});
    }
    
    return value;
     

    七.GT性能测试方案之电量测试

    1.简单流程

    • 关注指标:
      电量测试关注的指标有四个: 电流,电压,电量跟温度。
    • 数据获取方式:
      通过ReadPowerTimerTask任务去set关注的电量指标,当update方法调用时,才把数据set进去。电量数据调用的系统命令/sys/class/power_supply/battery/uevent
    • 具体流程:
      • 接收"com.tencent.wstt.gt.plugin.battery.startTest"广播后,update各个指标
      • 注册跟设置出参,并设置刷新频率跟初始化屏幕电量
      • 开启定时任务ReadPowerTimerTask,这个任务的作用就是去获取/sys/class/power_supply/battery/uevent下的电量数据并解析,最后设置出参。刷新频率默认是250ms一次
      • 最后stop时,销毁异步定时任务

    2.代码流程

    整个生命周期如下,当BATTERY_START_TEST行为被捕获时,开始执行电量测试

    String action = intent.getAction();
    if (action == null) return;
    if (action.equals(BATTERY_START_TEST)) {
       int refreshRate = intent.getIntExtra("refreshRate", 250);
       int brightness = intent.getIntExtra("brightness", 100);
    
       boolean updateI = intent.getBooleanExtra("I", true);
       GTBatteryEngine.getInstance().updateI(updateI);
    
       boolean updateU = intent.getBooleanExtra("U", false);
       GTBatteryEngine.getInstance().updateU(updateU);
    
       boolean updateT = intent.getBooleanExtra("T", false);
       GTBatteryEngine.getInstance().updateT(updateT);
    
       boolean updateP = intent.getBooleanExtra("P", false);
       GTBatteryEngine.getInstance().updateP(updateP);
    
       GTBatteryEngine.getInstance().doStart(refreshRate, brightness);
    } else if (action.equals(BATTERY_END_TEST)) {
       GTBatteryEngine.getInstance().doStop();
    }
     
    • update操作很简单,只是注册跟set出参
    public void updateI(boolean isChecked)
    {
       if (isChecked)
       {
          globalClient.registerOutPara(GTBatteryEngine.OPI, "I");
          globalClient.setOutparaMonitor(GTBatteryEngine.OPI, true);
       }
       else
       {
          globalClient.unregisterOutPara(GTBatteryEngine.OPI);
       }
       state_cb_I = isChecked;
       GTPref.getGTPref().edit().putBoolean(GTBatteryEngine.KEY_I, isChecked).commit();
    
       for (BatteryPluginListener listener : listeners)
       {
          listener.onUpdateI(isChecked);
       }
    }
     
    • 初始化操作略过,这里展示异步任务,处理流程直接看下面的关键代码即可,方法在GTBatteryEngine.java
    timer = new Timer(true);
    timer.schedule(new ReadPowerTimerTask(), refreshRate, refreshRate);
    
    @Override
    public void run() {
    
       BufferedReader br = null;
       try {
          FileReader fr = new FileReader(f);
          br = new BufferedReader(fr);
          String line = "";
          while((line = br.readLine()) != null){
    
             int found = 0;
             if (line.startsWith("POWER_SUPPLY_VOLTAGE_NOW="))
             {
                U = line.substring(line.lastIndexOf("=") + 1);
                // since 2.1.1 从μV转成mV
                long volt = Long.parseLong(U) / 1000;
                globalClient.setOutPara(OPU, volt + "mV");
    
                OutPara op = globalClient.getOutPara(OPU);
                if (null != op)
                {
                   OpPerfBridge.addHistory(op, U, volt);
                }
    
                found++;
             }
             if (line.startsWith("POWER_SUPPLY_CURRENT_NOW="))
             {
                I = line.substring(line.lastIndexOf("=") + 1);
                // since 2.1.1 从μA转成mA since 2.2.4 华为本身就是mA
                long current = Long.parseLong(I);
                if (isHuawei)
                {
                   current = -current;
                }
                else if (isLGg3)
                {
                   current = current >> 1; // 经验值估算LG g3的数据除以2后比较接近真实
                }
                else
                {
                   current = current / 1000;
                }
                globalClient.setOutPara(OPI, current + "mA");
    
                OutPara op = globalClient.getOutPara(OPI);
                if (null != op)
                {
                   OpPerfBridge.addHistory(op, I, current);
                }
    
                found++;
             }
             if (line.startsWith("POWER_SUPPLY_CAPACITY="))
             {
                String lastBattery = POW;
                POW =  line.substring(line.lastIndexOf("=") + 1);
                if (! lastBattery.equals(POW)) // 电池百分比变化了
                {
                   if (startBattry != -1)
                   {
                      lastBatteryChangeTime = (System.currentTimeMillis() - startBattry)/1000 + "s";
                      String tempValue = POW + "% | -1% time:" + lastBatteryChangeTime;
                      globalClient.setOutPara(OPPow, tempValue);
                      GTLog.logI(LOG_TAG, tempValue);
                      // 将电量加入历史记录
                      OutPara op = globalClient.getOutPara(OPPow);
                      if (null != op)
                      {
                         OpPerfBridge.addHistory(op, tempValue, Long.parseLong(POW));
                      }
                   }
    
                   startBattry = System.currentTimeMillis();
                }
    
                globalClient.setOutPara(OPPow, POW + "% | -1% time:" + lastBatteryChangeTime);
                found++;
             }
             if (line.startsWith("POWER_SUPPLY_TEMP="))
             {
                TEMP = line.substring(line.lastIndexOf("=") + 1);
                int iTemp = Integer.parseInt(TEMP);
                iTemp = iTemp/10;
                if (iTemp > -273)
                {
                   TEMP = iTemp + "℃";
                }
    
                globalClient.setOutPara(OPTemp, TEMP);
    
                OutPara op = globalClient.getOutPara(OPTemp);
                if (null != op && iTemp != INT_TEMP)
                {
                   OpPerfBridge.addHistory(op, TEMP, iTemp);
                   GTLog.logI(LOG_TAG, TEMP);
                   INT_TEMP = iTemp;
                }
    
                found++;
             }
             if (found >= 4)
             {
                return;         
    }      
    
    }   
    } catch (Exception e) {      
    doStop();   
    }   
    finally   
    {      
    FileUtil.closeReader(br);   
    }
    }

    GT Tools支持AndroidJUnit的测试脚本用于性能指标的采集和数据监控,但是也需要你自己去调用对应的API。
    另一种集成方式就是自己简单封装一层,用广播模式调用GT:http://gt.qq.com/docs/a/UseGtWithBroadcast.txt ,
    然后去驱动自己的用例,我在这里有一个整体介绍:https://testerhome.com/topics/9092
  • 相关阅读:
    hdu4846 最大子正方形(dp)
    hdu4847 水题
    hdu4847 水题
    hdu4848 DFS 暴搜+ 强剪枝
    hdu4848 DFS 暴搜+ 强剪枝
    洛谷 P4999 烦人的数学作业(数位DP)
    洛谷 P4317 花神的数论题(数位DP || 快速幂)
    洛谷 P2657 [SCOI2009]windy数(数位DP)
    洛谷 P2602 [ZJOI2010]数字计数(数位DP)
    HDU 2089 不要62(数位DP)
  • 原文地址:https://www.cnblogs.com/Ronaldo-HD/p/9922159.html
Copyright © 2011-2022 走看看